ResumeJSON

Automated resume screening: parse first, then rules

Automated resume screening: build it on parsed data, with rules you can explain

Automated resume screening is two jobs, and the first one decides whether the second can work. First you turn every CV into the same structured record: name, contact details, each job with its dates, education, skills, certifications and languages. Then you run explicit rules over those records to sort applicants into "clearly meets the requirements", "needs a human look" and "clearly missing a hard requirement". Screening done this way is fast, cheap to run and easy to explain to the candidate you turned down. Screening done by pasting CVs into a model and asking for a score is none of those things.

This article is for the developer adding screening to a job board, an applicant tracking system or an internal hiring tool. It walks the whole job, start to finish, with the tools you already have where they are enough. We build ResumeJSON, a parsing API that does the first half, so read the parsing section as written by an interested party. The screening half is yours to own whichever parser you use.


What automated resume screening actually needs

Before any code, write down what "screened" means for the role. Most screening rules fall into five groups, and each needs a different field out of the CV:

Rule typeExampleField it reads
Hard requirementHolds a nursing licencecertifications[].name
Experience floorAt least 3 years in the fieldwork[] dates, or a computed total
Skill matchKnows PostgreSQLskills[]
LogisticsBased in, or willing to move to, Berlinbasics.location
LanguageProfessional Germanlanguages[].language, proficiency

If a rule cannot point at a field, it is not a rule yet. "Good culture fit" and "strong communicator" do not live in a CV as data. Leave them to the interview, where a person can judge them.

The table also shows why raw text does not work well here. "3 years of experience" is arithmetic over a list of jobs with start and end dates, overlaps and gaps. A keyword search over the PDF text cannot do that arithmetic, and a model asked to do it in prose will get it wrong often enough to matter.

Step 1: get every CV into the same shape

Screening rules are only as good as the record they read. You have three honest ways to get that record.

  1. Ask the candidate to type it. An application form with structured fields (job title, employer, start month, end month) gives you clean data for free. The cost is drop-off: every extra field loses applicants, and people already have a CV they would rather upload.
  2. Parse it yourself. Extract the text from PDF and DOCX, then pull fields out with rules or a model. We have walked this path in Python, Node.js and with ChatGPT. It works, and the long tail (two-column layouts, scanned CVs, dates written five different ways) is where the time goes.
  3. Call a parsing API. Send the file, get typed JSON back. You trade a per-document fee for not maintaining the extraction.

Whichever you pick, normalise to one schema before any rule runs. A screening rule written against "whatever this parser returned" breaks the day you change parsers.

Here is the shape ResumeJSON returns for one role, trimmed:

{
  "resume": {
    "basics": { "full_name": "Dana Kim", "location": "Berlin, Germany", "email": "dana@example.com" },
    "work": [
      { "company": "Northwind", "title": "Backend Engineer", "start_date": "2021-03", "end_date": null, "is_current": true }
    ],
    "skills": ["PostgreSQL", "Go", "Kubernetes"],
    "certifications": [],
    "languages": [{ "language": "German", "proficiency": "Professional" }],
    "total_years_experience": 4.5
  }
}

Two details matter for screening. Dates come as YYYY-MM or YYYY, so your code can compare them without guessing at formats. And total_years_experience is computed from the work dates by our code, rather than read off whatever the candidate claimed in their summary, so an experience floor compares against something derived the same way for everyone.

Step 2: write the rules as code, not as a prompt

Once every applicant is a record, screening is ordinary code. A minimal version in TypeScript:

type Verdict = 'meets' | 'review' | 'missing';

function screen(r: Resume, role: Role): { verdict: Verdict; reasons: string[] } {
  const reasons: string[] = [];

  for (const cert of role.requiredCertifications) {
    const has = r.certifications.some(c => c.name?.toLowerCase().includes(cert.toLowerCase()));
    if (!has) reasons.push(`No ${cert} certification listed`);
  }

  const years = r.total_years_experience;
  if (years === null) reasons.push('Experience could not be computed from the dates given');
  else if (years < role.minYears) reasons.push(`${years} years of experience, role asks for ${role.minYears}`);

  const missingSkills = role.mustHaveSkills.filter(
    s => !r.skills.some(k => k.toLowerCase() === s.toLowerCase())
  );
  if (missingSkills.length) reasons.push(`Skills not listed: ${missingSkills.join(', ')}`);

  if (reasons.length === 0) return { verdict: 'meets', reasons };
  return { verdict: years === null ? 'review' : 'missing', reasons };
}

Three things this does on purpose:

Step 3: route, do not reject

The single most useful design choice in automated resume screening is what the machine is allowed to do on its own. Let automation move candidates forward and sort the queue. Let a person make every rejection.

In practice that means three lanes:

LaneWhat happensWho acts
MeetsMoves to the recruiter's shortlist, top of the queueRecruiter confirms
ReviewStays in the queue with the reasons shownRecruiter decides
MissingSorted to the bottom, reasons shownRecruiter confirms before any rejection email

This keeps most of the time saving, because the recruiter reads the "meets" lane first and skims the "missing" lane with the reasons already written. It also keeps a person accountable for each "no". Some places regulate automated decisions in hiring and may require audits or notices to candidates. Check the rules where you hire before any rejection goes out without a human.

Step 4: check the screen against people, not against itself

Before you trust the rules, run them over a batch of past applicants whose outcomes you already know. Look for two things:

Keep a sample of parsed records next to their source files as a regression set. When you change a rule or a parser, re-run the set and diff the verdicts.

Doing it in bulk: screening a backlog

A new role often arrives with hundreds of applications already waiting. The parsing half of that is a loop over documents, and we covered it in detail in bulk resume parsing. The short version for screening:

  1. Parse each CV once and store the JSON beside the file, keyed by a stable id.
  2. Run the rules over the stored records, not over fresh parses. Rules change often, CVs do not, and re-running rules costs nothing.
  3. Store the verdict and its reasons with the rule version that produced them, so you can tell which applicants were screened by which version.

Separating parsing from screening this way means tuning a rule never costs another parse.

When the manual way is enough

Not every hiring pipeline needs automated resume screening. Reading CVs yourself is the better choice when:

In those cases a structured record still helps. Parsing the CVs into a sortable table (name, current title, location, years of experience) makes manual reading faster without letting a machine decide anything. Our free parser handles a single CV in the browser with no signup, which is enough to see whether the fields are what you need.

Where ResumeJSON fits

ResumeJSON is the parsing step. It does not score, rank or reject anyone, and that is by design: the rules that decide who moves forward belong in your code, where you can read, test and explain them. What it gives you is the record those rules read:

As of September 2026 the API is sold through RapidAPI. The Basic plan is $0 for 100 parses a month with a hard cap, Pro is pay per use at $0.05 a parse, and paid plans start at $29 a month for 1,000 parses. The docs carry the current table and the full field reference.

Start with the rules, not the tooling. Write down what "meets the requirements" means for one role, map each requirement to a field, and then pick the parser that fills those fields reliably. The screening that follows is a function you own.

All articles