Unified.to
All articles

How-to Build an AI-Powered Candidate Assessment & Interview Scheduler


May 7, 2025

Let's build an application that scores a candidate's resume using an AI LLM and then schedule an interview if they pass a certain threshold, all with Unified's APIs.

You can see a video of this article at https://youtu.be/0hfH39vh7Qk

Overview of the system

The system we are creating today will be able to perform the following tasks:

  • Fetch information of candidates via ATS
  • Score candidates based on information they submitted and job requirements using AI
  • Automatically retrieve scheduling links and send them to chosen candidates

Requirements

We will need the following things setup to get started:

  • Node JS Installed on your machine (version 19 or higher)
  • Unified.to account with active connections for:
    • Greenhouse (ATS)
    • OpenAI (GenAI)
    • Calendly

Let's get going!


Step 1: Setting up Project

Create your project and initialize Node in the workspace by running the following command:

mkdir ai-job-automation
cd ai-job-automation
npm init -y
npm install express dotenv node-fetch

We will be using node's built-in fetch to communicate with Unified.to's API, but you can also use Unified.to's Typescript SDK.

Step 2: Setting up your development Environment

In order to setup your development environment, you must get the following connection IDs from Unified.

  1. Unified API Key (Settings -> API Keys)
  2. OpenAI Connection ID (In the connections page)
  3. Calendly Connection ID (In the connections page)
  4. Greenhouse Connection ID (In the connections page)

Once you obtain these connection IDs, place them in your .env file as follows.

UNIFIED_API_KEY=your_unified_api_key
CONNECTION_GPT=your_gpt_connection_id
CONNECTION_Calendly=your_calendly_connection_id
CONNECTION_GREENHOUSE=your_greenhouse_connection_id
PORT=3000

Step 3: Setup your Main Application

For this example we will be using a single file system, with various supporting functions. Create your main application file (app.js).

Import your dependencies

import 'dotenv/config';
import express from 'express';
import fetch from 'node-fetch';

const app = express();
app.use(express.json());

// Load environment variables
const {
  UNIFIED_API_KEY,
  CONNECTION_GPT,
  CONNECTION_Calendly,
  CONNECTION_GREENHOUSE,
  PORT
} = process.env;

Step 4: Create AI Scoring function

We will now be creating the scoreCandidate function, which will score and return each candidate, using Unified's GenAI Integration.

// Function to score candidate profiles using Unified GenAI (GPT-4o)

async function scoreCandidate(candidate, jobDescription) {
  const candidateProfile = `
    Name: ${candidate.name}    Title: ${candidate.title}    Company: ${candidate.company_name}    Experiences: ${JSON.stringify(candidate.experiences)}    Education: ${JSON.stringify(candidate.education)}  `;

  const prompt = `
    Job Description:
    ${jobDescription}    Candidate Profile:
    ${candidateProfile}    Provide a match score (0-100). The passing threshold is 75. Be lenient, especially if they have relevant experience. Pass at least 30% of candidates. Provide a brief explanation in the following format:
    Score: <number>
    Explanation: <brief explanation>
  `;

  const response = await fetch(`https://api.unified.to/genai/${CONNECTION_GPT}/prompt`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${UNIFIED_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      messages: [{ role: "USER", content: prompt }],
      max_tokens: 512,
      temperature: 0.2
    })
  });

  if (!response.ok) {
    const errorDetails = await response.text();
    throw new Error(`Error scoring candidate: ${response.statusText} - ${errorDetails}`);
  }

  const result = await response.json();
  const content = result.choices[0]?.message?.content;

  const scoreMatch = content.match(/Score:\s*(\d+)/i);
  const explanationMatch = content.match(/Explanation:\s*(.+)/i);

  const score = scoreMatch ? parseInt(scoreMatch[1], 10) : null;
  const explanation = explanationMatch ? explanationMatch[1].trim() : "No explanation provided.";

  return { score, explanation };
}

Candidates who pass the initial screening through the AI will automatically have a scheduling link retrieved through Unified's Calendly integration.

// Function to retrieve Calendly scheduling link via Unified integration

async function retrieveSchedulingLink(linkId) {
  const response = await fetch(`https://api.unified.to/calendar/${CONNECTION_Calendly}/link/${linkId}`, {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${UNIFIED_API_KEY}`,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    const errorDetails = await response.text();
    throw new Error(`Error retrieving scheduling link: ${response.statusText} - ${errorDetails}`);
  }

  const linkData = await response.json();
  return linkData.url;
}

Step 6: Main Candidate processing function (Webhook handler)

This function receives candidate information through Unified's webhook and processes candidates using the scoreCandidate & retrieveSchedulingLink functions we created above. Basically, each time that

app.post('/webhook', async (req, res) => {
    const event = req.body;
    const candidates = event.data;

    for (const candidate of candidates) {
      const candidateName = candidate.name;
      const candidateEmail = candidate.emails[0]?.email;

      try {
        // you can also get the Job description from the ATS as well
        const jobDescription = "Looking for someone competent in Mathematics and Computing to work as an IT intern in a bank";
        const aiResult = await scoreCandidate(candidate, jobDescription);

        console.log(`Candidate ${candidateName} scored ${aiResult.score}: ${aiResult.explanation}`);

        if (aiResult.score >= 75) {
          const schedulingLink = await retrieveSchedulingLink('your_link_id_here');

          console.log(`Scheduling link retrieved: ${schedulingLink}`);

          // workflow with your preferred email partner to send the email
          // Example: await sendEmail(candidateEmail, candidateName, schedulingLink);
        } else {
          console.log(`Candidate ${candidateName} did not meet the threshold.`);
        }
      } catch (error) {
        console.error(`Error processing candidate ${candidateName}:`, error.message);
      }
    }

    res.status(200).send('Webhook received successfully');
});

Step 7: Automatically create webhook subscription (No UI needed!)

Your webhook subscription will now be automatically created when your server starts.

let WEBHOOK_ID;

app.listen(PORT, async () => {
  console.log(`Webhook server running on port ${PORT}`);

  const webhookUrl = 'https://your-public-webhook-url.com/webhook'; // Replace with your actual public URL

  try {
    const webhookSubscription = await fetch(`https://api.unified.to/unified/webhook`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${UNIFIED_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 
          hook_url: webhookUrl, 
          event: 'created', 
          object_type: 'ats_candidate',
          connection_id:  CONNECTION_GREENHOUSE
        })
    });

    const webhookData = await webhookSubscription.json();
    console.log('Webhook subscription created successfully:', webhookData);
    WEBHOOK_ID = webhookData.id;
  } catch (error) {
    console.error('Failed to create webhook subscription:', error.message);
  }
});

// unsubscribe when exiting
process.on('SIGINT', async () => {
  await fetch(`https://api.unified.to/unified/webhook/${WEBHOOK_ID}`, {
    method: 'DELETE'
  });
});

That is it. You are ready to rip this example up and build your own AI-powered candidate assessment and interviewing solution.

All articles