ATS to Vector DB: How to Power Talent Intelligence with Real-Time Data
October 14, 2025
Updated June 2026
With Unified.to, you can build a talent intelligence application that works with your customers' preferred ATS — Lever, Greenhouse, and 80+ others — through one integration.
With a single API, you fetch candidate records, normalize and embed resumes, and upsert those embeddings into a vector database like Pinecone for semantic search and recruiter-agent workflows. The result is a retrieval layer for talent intelligence, built on ATS data fetched live from the source.
This guide shows you how to go from ATS to vector DB, step by step, using Unified.to, its GenAI API, and Pinecone.
What this builds
- Fetch normalized candidate data from an ATS.
- Chunk and embed resume content.
- Store embeddings in a vector database.
- Retrieve the most relevant candidates at query time.
- Use the retrieved context to power recruiter search, ranking, or AI agents.
Unified.to handles ingestion and live updates across ATS providers; the embeddings and vector storage stay in your infrastructure. Because Unified.to is pass-through, no candidate data rests on its servers — the records flow to your retrieval layer and stop there.
Prerequisites
- Node.js (v18+)
- A Unified.to account with an ATS integration enabled (e.g., Lever, Greenhouse)
- A Unified.to API key
- Your customer's ATS connection ID
- A Unified.to GenAI connection ID (for embeddings)
- A Pinecone API key and index
Step 1: Set up your project
mkdir ats-vector-demo
cd ats-vector-demo
npm init -y
npm install @unified-api/typescript-sdk dotenv @pinecone-database/pinecone
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_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_INDEX,
} = process.env;
const sdk = new UnifiedTo({
security: { jwt: UNIFIED_API_KEY! },
});
const pinecone = new Pinecone({ apiKey: PINECONE_API_KEY! });
const index = pinecone.Index(PINECONE_INDEX!);
Step 3: Get your customer's connection ID
Before you can fetch candidates, your customer authorizes your app to access their ATS (e.g., Lever, Greenhouse) through Unified.to's embedded authorization flow. Once authorized, you receive a connection ID for that customer's integration. Store it securely and use it in all API calls for that customer.
Step 4: Fetch and normalize candidate records
Fetch candidates from the ATS and flatten each into a single text block for embedding. Normalizing the resume text before embedding keeps embeddings consistent across every ATS.
import type { AtsCandidate } from '@unified-api/typescript-sdk/models/components';
export async function fetchCandidates(connectionId: string): Promise<AtsCandidate[]> {
return await sdk.ats.listAtsCandidates({
connectionId,
limit: 50,
});
}
export function normalizeResume(candidate: AtsCandidate): string {
const experiences = (candidate.experiences ?? [])
.map((exp: any) => `${exp.title ?? ''} at ${exp.company_name ?? ''}`)
.join('; ');
const education = (candidate.education ?? [])
.map((edu: any) => `${edu.degree ?? ''} in ${edu.field_of_study ?? ''} from ${edu.institution ?? ''}`)
.join('; ');
return [
`Name: ${candidate.name ?? ''}`,
`Email: ${candidate.emails?.[0]?.email ?? ''}`,
`Title: ${candidate.title ?? ''}`,
`Skills: ${(candidate.skills ?? []).join(', ')}`,
`Experience: ${experiences}`,
`Education: ${education}`,
].join('\n');
}
Step 5: Embed resumes with the GenAI API
Use Unified.to's GenAI embedding endpoint. Note content is an array, encoding_format takes FLOAT, and type distinguishes documents you index (SEARCH_DOC) from the recruiter query (SEARCH_QUERY). The embeddings field returns a JSON string, so parse it before use.
export async function embed(text: string, kind: 'SEARCH_DOC' | 'SEARCH_QUERY'): Promise<number[]> {
const result = await sdk.genai.createGenaiEmbedding({
connectionId: CONNECTION_GENAI!,
genaiEmbedding: {
modelId: 'text-embedding-3-small',
content: [text],
// If the SDK rejects this key, the upstream schema spells it `enconding_format`;
// the mechanically camelCased form is then `encondingFormat`. One live call confirms which.
encodingFormat: 'FLOAT',
type: kind,
dimension: 1536,
},
});
// `embeddings` is a read-only JSON string per the GenAI model.
return JSON.parse(result.embeddings ?? '[]');
}
Keeping the vector index current
To keep your retrieval layer fresh, subscribe to ATS webhooks for candidate create and update events. When a resume changes, re-fetch the candidate, re-embed, and upsert. Unified.to manages native and virtual webhooks, so you receive these events even when the ATS has no native webhook support.
Step 6: Upsert embeddings to Pinecone
export async function upsertCandidate(candidate: AtsCandidate, values: number[]) {
await index.upsert([
{
id: candidate.id!,
values,
metadata: {
name: candidate.name ?? '',
email: candidate.emails?.[0]?.email ?? '',
candidate_id: candidate.id ?? '',
},
},
]);
}
Step 7: Retrieval
Given a recruiter query, embed it with SEARCH_QUERY and search Pinecone for the closest candidates.
export async function searchCandidates(query: string) {
const queryVector = await embed(query, 'SEARCH_QUERY');
const results = await index.query({
vector: queryVector,
topK: 5,
includeMetadata: true,
});
return results.matches;
}
Step 8: Putting it together
async function main() {
const candidates = await fetchCandidates(CONNECTION_ATS!);
for (const candidate of candidates) {
const resumeText = normalizeResume(candidate);
const values = await embed(resumeText, 'SEARCH_DOC');
await upsertCandidate(candidate, values);
}
const matches = await searchCandidates('Senior Python developer with fintech experience');
console.log('Top matches:', matches);
}
main();
What you built
- A single API call fetches candidate records from any ATS (Lever, Greenhouse, and 80+ more).
- Resumes are normalized and embedded through Unified.to's GenAI API, then upserted into Pinecone.
- Recruiters search in natural language and retrieve the most relevant candidates — over data fetched live from the source, with nothing cached on Unified.to's side.
For the product-level walkthrough, see How to Build a Candidate Assessment Product with Unified.to. To wire up sourcing, see How to Build Candidate Sourcing with a Unified API.
Ready to build? Sign up for a free 30-day trial or book a demo.