Unified.to
All articles

ATS to Vector DB: How to Power Talent Intelligence with Real-Time Data


October 14, 2025

With Unified, you can a build talent intelligence application that work with your customers' preferred ATS platform, such as Lever and Greenhouse.

With a single API integration, you can fetch candidate records, normalize and embed resumes, and then push those embeddings into a vector database like Pinecone for real-time search and recruiter agent workflows.

This guide shows you how to go from ATS to vector DB, step by step, using Unified, GenAI, and Pinecone.


Prerequisites

  • Node.js (v18+)
  • Unified account with ATS integration enabled (e.g., Lever, Greenhouse)
  • Unified API key
  • Your customer's ATS connection ID
  • Unified GenAI connection ID (or OpenAI API key for embeddings)
  • Pinecone API key and environment

Step 1: Setting up your project

As always, let's get the basics out of the way!

mkdir ats-vector-demo
cd ats-vector-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv @pinecone-database/pinecone openai

Add your credentials to .env:

UNIFIED_API_KEY=your_unified_api_key
CONNECTION_ATS=your_customer_ats_connection_id
CONNECTION_GENAI=your_genai_connection_id
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_ENVIRONMENT=your_pinecone_env
PINECONE_INDEX=your_pinecone_index

Step 2: Initialize the SDKs

import 'dotenv/config';
import { UnifiedTo } from '@unified-api/typescript-sdk';
import { Pinecone } from '@pinecone-database/pinecone';

const {
  UNIFIED_API_KEY,
  CONNECTION_ATS,
  CONNECTION_GENAI,
  PINECONE_API_KEY,
  PINECONE_ENVIRONMENT,
  PINECONE_INDEX
} = process.env;

const sdk = new UnifiedTo({
  security: { jwt: UNIFIED_API_KEY! },
});

const pinecone = new Pinecone({
  apiKey: PINECONE_API_KEY!,
  environment: PINECONE_ENVIRONMENT!,
});
const index = pinecone.Index(PINECONE_INDEX!);

Step 3: How to Get Your Customer's Connection ID

Before you can fetch candidates, your customer must authorize your app to access their ATS (e.g., Lever, Greenhouse) via Unified's embedded authorization flow.

Once authorized, you'll receive a connection ID for that customer's integration.

Store this connection ID securely and use it in all API calls for that customer.


Step 4: Fetch and Normalize Candidate Records

Fetch candidate records from the ATS and normalize their resumes for embedding.

export async function fetchCandidates(connectionId: string) {
  const candidatesResult = await sdk.ats.listAtsCandidates({
    connectionId,
    limit: 10,
  });
    return candidatesResult; // AtsCandidate[]
}

export function normalizeResume(candidate: any): string {
  return [
    `Name: ${candidate.name}`,
    `Email: ${candidate.emails?.[0]?.email || ""}`,
    `Title: ${candidate.title || ""}`,
    `Summary: ${candidate.summary || ""}`,
    `Experience: ${(candidate.experiences || []).map((exp: any) => `${exp.title} at ${exp.company_name}`).join("; ")}`,
    `Education: ${(candidate.education || []).map((edu: any) => `${edu.degree_name} from ${edu.institution_name}`).join("; ")}``
  ].join('\\n');
}

Step 5: Embed Resumes with GenAI

Use Unified GenAI to embed the normalized resume text.

export async function embedResume(resumeText: string) {
  const result = await sdk.genai.createGenaiEmbedding({
    connectionId: CONNECTION_GENAI!,
    embedding: {
      content: {
        text: resumeText,
      },
      model_id: 'text-embedding-3-small',
      maxTokens: 1024,
      enconding_format: 'FLOAT'
    },
  });
  return result.embeddings;
}

Step 6: Push Embeddings to Pinecone

export async function upsertCandidateToPinecone(candidate: any, embedding: number[]) {
  await index.upsert([
    {
      id: candidate.id,
      values: embedding,
      metadata: {
        name: candidate.name,
        email: candidate.emails?.[0]?.email || "",
        candidate_id: candidate.id,
      },
    },
  ]);
}

Step 7: Simple Retrieval Flow

Given a recruiter query, embed it and search Pinecone for the most relevant candidates.

export async function searchCandidates(query: string) {

  const result = await sdk.genai.createGenaiPrompt({
    connectionId: CONNECTION_GENAI!,
    prompt: {
      messages: [{ role: "USER", content: `Embed this query:\\n${query}` }],
      maxTokens: 1024,
      temperature: 0.0,
      responseFormat: "embedding",
    },
  });
  const queryEmbedding = result.choices?.[0]?.message?.embedding || [];


  const pineconeResults = await index.query({
    vector: queryEmbedding,
    topK: 5,
    includeMetadata: true,
  });

  return pineconeResults.matches;
}

Step 8: Example Usage

async function main() {

  const candidates = await fetchCandidates(CONNECTION_ATS!);
  for (const candidate of candidates) {
    const resumeText = normalizeResume(candidate);
    const embedding = await embedResume(resumeText);
    await upsertCandidateToPinecone(candidate, embedding);
  }


  const matches = await searchCandidates("Senior Python developer with fintech experience");
  console.log("Top matches:", matches);
}

main();

What just happened?

  • And just like that you can fetch candidate records from any ATS (Lever, Greenhouse, etc.) using a single API.
  • You can then normalize and embed resumes with Unified GenAI, and push them into Pinecone for real-time search.
  • Recruiters can then search for talent using natural language, and retrieve the most relevant candidates instantly.

Happy Building! 🎉

All articles