← Blog

How to Build a Resume Screener With Claude

2026-10-26 · 5 min read · SubToAPI Team

Building a resume screener with Claude means feeding candidate resumes and a job description into the model, then getting back structured, consistent output — a fit score, extracted skills, flags for missing requirements — instead of a wall of prose. The core pattern is: extract text from the resume, construct a prompt that pins down the exact JSON schema you want, call Claude with tool use or a strict response format, and store the result for ranking.

This is a good fit for Claude because resume screening is really a text classification and extraction problem wrapped in natural language nuance — synonyms for skills, non-standard formatting, varying seniority language. A rules-based keyword matcher misses too much; a well-prompted LLM handles it reliably if you constrain the output.

The screening pipeline

A production-grade resume screener typically has four stages:

  1. Text extraction — convert PDF/DOCX resumes to plain text (use a library like pdf-parse or pdfplumber before this ever touches Claude).
  2. Structured extraction — ask Claude to pull out name, years of experience, skills, education, and job history into JSON.
  3. Scoring against a job description — pass the extracted profile plus the JD and ask for a fit score with reasoning.
  4. Ranking and storage — persist scores so a recruiter can sort candidates.

Steps 2 and 3 can be combined into one call for lower latency, but keeping them separate makes debugging easier and lets you reuse the extracted profile for multiple job openings.

Step 1: Extract resume text

Do this outside the LLM call. Dumping raw PDF bytes into a prompt wastes tokens and loses structure. A simple Node extraction:

import pdf from "pdf-parse";
import fs from "fs";

async function extractText(filePath) {
  const buffer = fs.readFileSync(filePath);
  const data = await pdf(buffer);
  return data.text;
}

Step 2: Define the output schema with tool use

Use tool use (function calling) to force Claude into a predictable JSON shape rather than hoping it follows formatting instructions in plain text. This is the most important reliability lever in the whole pipeline.

const tools = [
  {
    name: "score_resume",
    description: "Extract candidate data and score fit against a job description",
    input_schema: {
      type: "object",
      properties: {
        candidate_name: { type: "string" },
        years_experience: { type: "number" },
        skills: { type: "array", items: { type: "string" } },
        education: { type: "string" },
        missing_requirements: { type: "array", items: { type: "string" } },
        fit_score: { type: "number", description: "0-100 fit score" },
        reasoning: { type: "string" }
      },
      required: ["candidate_name", "fit_score", "reasoning"]
    }
  }
];

Step 3: Call the model

Here's a call using the SubToAPI endpoint, which proxies to Claude and adds application API keys, usage metadata, and streaming on top — useful if multiple teammates or services need to hit the screener and you want per-key usage tracking rather than sharing one raw credential.

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "score_resume",
      "description": "Extract candidate data and score fit against a job description",
      "input_schema": {
        "type": "object",
        "properties": {
          "candidate_name": {"type": "string"},
          "years_experience": {"type": "number"},
          "skills": {"type": "array", "items": {"type": "string"}},
          "education": {"type": "string"},
          "missing_requirements": {"type": "array", "items": {"type": "string"}},
          "fit_score": {"type": "number"},
          "reasoning": {"type": "string"}
        },
        "required": ["candidate_name", "fit_score", "reasoning"]
      }
    }],
    "tool_choice": {"type": "tool", "name": "score_resume"},
    "messages": [{
      "role": "user",
      "content": "Job description:\nSenior backend engineer, 5+ years Go, Kubernetes, distributed systems.\n\nResume:\n[resume text here]\n\nScore this candidate'\''s fit for the role."
    }]
  }'

Forcing tool_choice to the specific tool guarantees Claude replies with the structured object instead of chatting. The response will contain a tool_use block with the parsed fields ready to insert into your database — see /docs/tools for the full tool use reference and /docs/messages for the base request format.

Step 4: Rank candidates

Once you have fit_score for every applicant, sorting is trivial:

const ranked = scoredCandidates.sort((a, b) => b.fit_score - a.fit_score);

Store missing_requirements and reasoning alongside the score — recruiters trust the tool more when they can see why a candidate scored the way they did, and it gives you an audit trail if a screening decision is ever questioned.

Prompt design tips that actually matter

Where SubToAPI fits

If you're building this inside a company and want the Claude access already on your account, without juggling raw API keys scattered across scripts and teammates, SubToAPI turns your Claude subscription into application keys (sub_live_...) with per-key usage visibility — handy when your recruiting tool, your internal dashboard, and a teammate's quick script all need separate, revocable credentials. Check /pricing for Solo, Team, and Scale plans, or start with /signup and follow /docs/quickstart to get your first resume-screening call working in a few minutes.

questions

Can Claude read resumes directly from PDF files? Not natively through the Messages API — you need to extract text first with a library like pdf-parse or pdfplumber, then send the plain text to Claude. Image-based scanned resumes may need OCR before extraction.

How do I keep scoring consistent across hundreds of resumes? Use tool use with a forced tool_choice and a fixed JSON schema, and set temperature low (0–0.3). This removes most of the variance you'd otherwise get from open-ended text generation.

Is it legal to use AI to screen resumes? Rules vary by jurisdiction — some regions (e.g., NYC Local Law 144) require bias audits and candidate disclosure for automated hiring tools. Build in human review of final decisions and keep the model's reasoning stored for auditability.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →