How to build a Candidate Assessment product with Unified.to

October 23, 2023

In the high-paced realm of recruitment, where talent acquisition meets the evolving landscape of HR technology, the need for efficient and robust Candidate Assessment tools has never been more crucial. As companies strive to identify top talent, incorporating candidate assessment software that facilitates critical activities like background checks, skills tests, candidate analytics analysis and more not only expedites the screening process but also ensures a comprehensive and data-driven approach to talent acquisition.

This guide explore how to develop robust and scalable Candidate Assessment software by leveraging third-party integrations through Unified.to's Unified API developer platform to enhance the value of your candidate assessment solution.


You have built a Candidate Assessment solution for recruiters and hiring managers, enabling them to evaluate candidates who are applying for positions within their companies.

Why (your goal)

You need to access your customers' ATS (application tracking systems) to automate the data flow of new applications so that your solution can test and assess their candidates.


You will need to integrate seamlessly with the leading ATS solutions utilized by your customers, such as Greenhouse, Lever, SmartRecruiters, SAP SuccessFactors, and more. This will provide your users with a streamlined user experience, ensuring efficient data flow and synchronization between your Candidate Assessment solution and their preferred Applicant Tracking Systems (ATS).

How to add ATS integrations to your product

Before we start, be sure to first read:

Getting Started with Unified

  1. Get an initial list of applications
    1. When the customer authorizes an ATS connection, read the initial set of active applications
      import Unified from 'unified-ts-client';
      import { TAtsApplicationStatus, IAtsApplication } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified ({ api_token: process.env.UNIFIED_TOKEN });
      export async function getApplications(connection_id: string, trigger_status: TAtsApplicationStatus, job_id: string, updated_gte?: string) {
          const applications: IAtsApplication[] = [];
          const limit = 100;
          let offset = 0;
        while(true) {
              const apps = await unifiedClient.ats(connection_id).application.getAll({ updated_gte, job_id, offset, limit });
              applications.push(...apps.filter((application) => application.status === trigger_status));
              if (apps.length === limit) {
                  offset += limit;
              } else {
          return applications;
  2. Set up to get updated applications
    1. If you want to use webhooks to get new/updated applications in the future, create a webhook with that connection_id
      import { createHmac } from 'crypto';
      import Unified from 'unified-ts-client';
      import { IWebhookData, IWebhook } from 'unified-ts-client/lib/src/unified/types/Unified';
      import { IAtsApplication, TAtsApplicationStatus } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async function createApplicationsWebhook(connection_id: string, my_webhook_url: string): Promise<IWebhook> {
          const webhook = await unifiedClient.unified().webhook.create(connection_id, 'ats_application', my_webhook_url, 'updated');
        return webhook.id;
      export async function handleUnifiedWebhook(incoming: IWebhookData, trigger_status: TAtsApplicationStatus): Promise<IAtsApplication[]> {
        if (incoming.webhook.object_type !== 'ats_application') {
          return; // not for us
        const sig = createHmac('sha1', process.env.WORKSPACE_SECRET).update(JSON.stringify(incoming.data)).update(String(incoming.nonce)).digest('base64');
        if (sig !== incoming.sig) {
          return; // Houston, we have a problem... with security
        return incoming.data?.filter((application: IAtsApplication) => application.status === trigger_status);
    2. Alternatively, create a polling schedule to get new/updated applications, which would be similar to the code in 1a)
  3. Filter applications on a specific status
    1. The application has a standardized status across all ATS integrations, so decide which one makes sense for you to trigger the assessment
  4. Email the candidate with the assessment based on the job
    1. Read the candidate from the Application.candidate_id field
      import Unified from 'unified-ts-client';
      import { IAtsCandidate } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async readCandidate(connection_id: string, candidate_id: string): Promise<IAtsCandidate> {
          return await unifiedClient.ats(connection_id).candidate.getOne(candidate_id);
    2. Optional: read the job from the Application.job_id field
      import Unified from 'unified-ts-client';
      import { IAtsJob } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async readJob(connection_id: string, job_id: string): Promise<IAtsJob> {
          return await unifiedClient.ats(connection_id).job.getOne(job_id);
    3. Determine which test to send to the candidate based on the Job.descripton or Job.name fields
  5. Once the candidate has completed the assessment/test, notify the hiring manager and/or recruiter
    1. Read the employee information based on the Job.hiring_managers_ids and/or Job.recruiter_ids fields and use the Employee.emails field to email them the notification
      import Unified from 'unified-ts-client';
      import { IHrisEmployee } from 'unified-ts-client/lib/src/hris/types/UnifiedHris';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async readEmployee(connection_id: string, employee_id: string): Promise<IHrisEmployee> {
          return await unifiedClient.hris(connection_id).employee.getOne(employee_id);
    2. Optional: push back a PDF document into the customer's ATS that is associated with that application
      import Unified from 'unified-ts-client';
      import { IAtsDocument, TAtsDocumentType } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async function createDocument(
        connection_id: string,
        application_id: string,
        document_url: string,
        filename: string,
        type: TAtsDocumentType
      ): Promise<IAtsDocument> {
        return await unifiedClient.ats(connection_id).document.create({
    3. Optional: update a candidate with certain tags
      import Unified from 'unified-ts-client';
      import { IAtsDocument, TAtsDocumentType } from 'unified-ts-client/lib/src/ats/types/UnifiedAts';
      const unifiedClient = new Unified({ api_token: process.env.UNIFIED_TOKEN });
      export async function updateCandidateTags(connection_id: string, candidate_id: string, tags: string[]): Promise<IAtsCandidate> {
        return await unifiedClient.ats(connection_id).candidate.update(candidate_id, {

Keep learning:

One API to integrate them all

Unified.to is a complete solution to streamline your integration development process and power your Candidate Assessment product with critical third-party candidate data. You're reading this article on the Unified.to blog. We're a Unified API developer platform for SaaS customer-facing integrations. We're excited to continue to innovate at Unified.to and solve hard, critical integration-related problems for our customers. If you're curious about our integrations-as-a-service solution, consider signing up for a free account or meet with an integrations expert.