Unified.to
All articles

How to Match Candidates to Jobs Using ATS Data with Unified's ATS API


February 18, 2026

Matching candidates to jobs sounds like an algorithm problem.

In practice, it's a data modeling problem first.

Before you can score candidates, rank applicants, or train historical models, you need consistent access to:

  • Open job requirements
  • Candidate qualifications
  • Application relationships
  • Outcome signals

Across ATS systems, those elements are structured differently — and not always fully available.

This guide explains how to match candidates to jobs using Unified's ATS API, grounded strictly in what the API exposes and where provider variability applies.

The Matching Mental Model

Candidate-job matching depends on four data layers:

  1. Jobs (AtsJob) – requirements and context
  2. Candidates (AtsCandidate) – qualifications and profile data
  3. Applications (AtsApplication) – pipeline relationships
  4. Documents (AtsDocument) – resumes and attachments

Unified provides normalized models for each of these objects. However, field availability varies by integration and must be handled defensively.

Step 1: Ingest Open Jobs

Open jobs are your target set for matching.

Endpoint

GET /ats/{connection_id}/job

SDK example:

const jobs = await sdk.ats.listAtsJobs({
  connectionId,
  status: 'OPEN',
  sort: 'updated_at',
  order: 'asc',
  limit: 100,
  offset: 0,
  fields: '',
  raw: '',
});

Key Job Fields

From the normalized AtsJob model:

  • id
  • name
  • description
  • status
  • minimum_experience_years
  • minimum_degree
  • employment_type
  • remote
  • questions[]
  • metadata[]

Important Caveat: skills[]

Although skills[] exists in the normalized job schema, most ATS integrations do not expose it in readable fields.

Only a small subset of integrations (e.g., Vincere for candidates) expose structured skills. Many integrations omit skills[] entirely for both candidates and jobs.

Implication:

Do not rely on skills[] as your primary matching signal. Treat it as optional structured enrichment when available.

Step 2: Ingest Candidate Profiles

Candidates are ingested via:

GET /ats/{connection_id}/candidate

SDK example:

const candidates = await sdk.ats.listAtsCandidates({
  connectionId,
  sort: 'updated_at',
  order: 'asc',
  limit: 100,
  offset: 0,
  fields: '',
  raw: '',
});

Key Candidate Fields

From AtsCandidate:

  • first_name, last_name
  • title
  • company_name
  • experiences[]
  • education[]
  • skills[] (rarely populated)
  • tags[]
  • metadata[]
  • link_urls[]

Skills Field Variability

The skills[] field exists in the normalized schema, but most integrations do not populate it. Only certain providers (e.g., Vincere) provide candidate skills reliably.

Implication:

Treat skills[] as opportunistic data, not guaranteed data.

Step 3: Retrieve Resume Documents (When Available)

Resumes provide the richest unstructured matching signal.

Endpoint

GET /ats/{connection_id}/document

Important Facts

  • Most integrations return a short-lived document_url.
  • document_url typically expires after one hour.
  • Only a small subset of integrations (e.g., Workable, Vincere) return document_data (base64) on read.
  • Some integrations (e.g., Workday) do not expose document URLs or document data.
  • Some ATS integrations do not expose document objects at all.

Resume Identification

The normalized document schema includes:

type: 'RESUME' | 'COVER_LETTER' | ...

However, documentation does not guarantee that all providers consistently label resumes as RESUME.

Implication:

Resume retrieval depends on integration capability. Always check each provider's 'Readable Fields' list and handle missing documents gracefully.

Step 4: Join Candidates to Jobs via Applications

Applications link candidates to jobs.

GET /ats/{connection_id}/application

Key fields:

  • candidate_id
  • job_id
  • status
  • applied_at
  • rejected_at
  • hired_at
  • offers[]

Your internal model should reflect:

Candidate
  ↳ Applications[]
      ↳ Job

Matching may occur:

  • Before application (proactive sourcing)
  • After application (ranking within pipeline)
  • Post-interview (scoring finalists)

Step 5: Feature Extraction Strategy

Because structured skills are inconsistently available, matching should combine:

Structured signals (when available)

  • minimum_experience_years
  • minimum_degree
  • employment_type
  • remote
  • tags[]
  • metadata[]

Semi-structured signals

  • experiences[]
  • education[]
  • answers[] (from applications)

Unstructured signals

  • Resume text (if retrievable via document_url or document_data)
  • Job description text

Your matching pipeline may include:

  • Keyword overlap
  • Embedding similarity
  • Rule-based scoring
  • ML model scoring

Unified provides the ingestion layer. The scoring layer lives in your system.

Step 6: Use Historical Outcomes for Training

If building supervised models, you need labels.

Outcome-related fields in AtsApplication include:

  • hired_at
  • rejected_at
  • offers[]
  • offers[].accepted_at

Provider Variability

  • hired_at is not universally exposed.
  • rejected_at is inconsistently exposed.
  • Some providers return only rejected_reason.
  • offers[] is supported only in certain integrations.
  • When supported, offers is often marked as a slow field.
  • Documentation does not guarantee that outcome fields are populated across integrations.

Implication:

Always design training pipelines to handle missing outcome data.

Example labeling logic:

if (application.hired_at) label = 'HIRED';
else if (application.rejected_at) label = 'REJECTED';
else label = 'IN_PROGRESS';

But only when those fields are supported by the integration.

Step 7: Keep Matching Data Up to Date

All list endpoints support:

updated_gte

This enables incremental refresh:

await sdk.ats.listAtsCandidates({
  connectionId,
  updated_gte: lastSyncTimestamp,
  sort: 'updated_at',
  order: 'asc',
});

For near-real-time scoring updates, ATS webhooks can be subscribed to for:

  • ats_candidate
  • ats_application
  • ats_job
  • ats_document

Filter availability varies by integration and applies only to virtual webhooks.

Putting It Together

A defensible candidate-job matching pipeline looks like:

  1. Ingest open jobs.
  2. Ingest candidates.
  3. Retrieve documents when supported.
  4. Join via applications.
  5. Extract structured and unstructured features.
  6. Train or score candidates.
  7. Update scores incrementally.

Unified's ATS API provides the normalized data foundation. It does not impose a scoring model, dedupe logic, or cross-provider guarantees.

That separation is what makes it safe to build algorithmic matching across heterogeneous ATS systems.

Closing Thoughts

Matching candidates to jobs is not about having perfect data. It's about building resilient models on top of imperfect, provider-variable systems.

Unified gives you:

  • Consistent job and candidate objects
  • Application relationships
  • Resume access when supported
  • Incremental sync

From there, your algorithm determines the match — not the integration layer.

→ Start your 30-day free trial

→ Book a demo

All articles