How to Build a Claude-Powered Resume Screening Tool
Building a Claude-powered resume screening tool means using Claude's language understanding to parse resumes, extract structured candidate data, and score fit against a job description — then exposing that logic as an API your ATS, careers page, or internal tool can call. The core pattern is simple: send resume text and job requirements to Claude with a strict output schema, parse the structured response, and store or rank the result.
This article walks through the full build: prompt design, structured output, batching for volume, and turning the whole thing into a reliable HTTP endpoint instead of a one-off script. If you already have Claude access through Anthropic or a Claude subscription, you can skip building your own auth and rate-limiting layer and call it as a standard REST API using SubToAPI, which converts your existing Claude access into HTTPS endpoints with API keys, streaming, and usage tracking.
Why Claude works well for resume screening
Resumes are unstructured, inconsistent, and full of edge cases: PDFs with weird formatting, non-standard section headers, candidates listing skills in prose instead of bullet points. A keyword-matching ATS filter misses context — "led a team that migrated our data pipeline to Kafka" should count as Kafka experience even without the literal word "Kafka experience."
Claude handles this well because it reads resumes the way a human recruiter does: inferring seniority from responsibilities, recognizing equivalent skills, and judging overall fit rather than just matching strings. The trade-off is that you need to constrain its output carefully, or you'll get inconsistent formatting across hundreds of resumes.
Step 1: Define a strict extraction schema
Before writing any prompt, decide exactly what fields you need. A typical schema:
- Candidate name, email, phone (if present)
- Years of relevant experience
- Key skills (as a list)
- Education level
- Fit score (1–10) against the job description
- Short justification (1–2 sentences)
- Red flags (gaps, mismatches, missing requirements)
Locking this down first prevents prompt drift later, where Claude's output format subtly changes between runs.
Step 2: Write the screening prompt
Ask Claude to return JSON only, and be explicit about missing data:
You are a resume screening assistant. Given a job description and a resume,
extract structured candidate data and return ONLY valid JSON matching this shape:
{
"name": string,
"email": string | null,
"years_experience": number,
"skills": string[],
"education_level": string,
"fit_score": number,
"justification": string,
"red_flags": string[]
}
If a field cannot be determined, use null or an empty array. Do not include
any text outside the JSON object.
Then feed in the job description and resume text as separate labeled blocks in the user message. Keeping them labeled ("JOB DESCRIPTION:" / "RESUME:") reduces the chance Claude conflates requirements with candidate claims.
Step 3: Call the model
If you're routing through SubToAPI, the request looks like a standard POST:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 600,
"messages": [
{
"role": "user",
"content": "JOB DESCRIPTION:\n...\n\nRESUME:\n..."
}
]
}'
See /docs/messages for the full request/response reference, and /docs/quickstart if this is your first API call.
In JavaScript, wrapped as a reusable function:
async function screenResume(jobDescription, resumeText) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 600,
messages: [
{
role: "user",
content: `SYSTEM:\nReturn only JSON.\n\nJOB DESCRIPTION:\n${jobDescription}\n\nRESUME:\n${resumeText}`,
},
],
}),
});
const data = await res.json();
return JSON.parse(data.content[0].text);
}
Step 4: Validate the output, don't trust it blindly
Even with a strict schema, occasionally Claude will wrap JSON in markdown fences or add a stray sentence. Parse defensively:
function extractJson(text) {
const match = text.match(/\{[\s\S]*\}/);
if (!match) throw new Error("No JSON found in response");
return JSON.parse(match[0]);
}
Also run basic sanity checks after parsing — fit_score between 1 and 10, skills is an array, email matches a basic pattern or is null. Reject and retry once if validation fails before falling back to a human review queue.
Step 5: Use tool calling for multi-step screening
For more advanced screening — say, cross-referencing a candidate's GitHub profile or checking a skills database — Claude's tool use lets the model call out to your functions mid-reasoning instead of you chaining multiple prompts manually. Define a lookup_skill_taxonomy or verify_certification tool, and Claude decides when to invoke it while screening. Details are in /docs/tools.
Step 6: Handle volume with streaming and batching
Screening one resume at a time is fine for a trickle of applications, but a hiring push with hundreds of resumes needs either batching (send resumes in parallel with controlled concurrency, not all at once) or streaming partial results back to a dashboard so reviewers see scores as they complete. If you're building a live screening dashboard, streaming responses token-by-token keeps the UI responsive — see /docs/streaming for implementation details.
A practical concurrency pattern: process 5–10 resumes at a time with Promise.allSettled, log failures separately, and retry only the failed batch rather than the whole set.
Step 7: Wrap it as an internal API
Once the logic works, don't leave it as a script someone runs manually. Expose it as an internal endpoint (POST /screen) that your ATS or recruiting team's internal tools call directly. This is where using an existing Claude subscription as an API pays off — no separate Anthropic billing account, API key sprawl, or infrastructure to manage. SubToAPI gives you sub_live_... keys, per-team usage metadata, and seat management out of the box, which matters once more than one recruiter or engineer needs access. Plans start at €9/month for solo use, with team and scale tiers for larger hiring pipelines — see /pricing.
Keep a human in the loop
Resume screening with an LLM should narrow the pool, not make final decisions. Treat fit_score and red_flags as triage signals that route candidates to recruiters faster, and always allow manual override. This also reduces legal and fairness risk compared to fully automated rejection.
FAQs
Can Claude read resumes directly from PDF files? Not natively as binary PDFs in the API — you need to extract text first using a PDF parsing library, then send the plain text to Claude. OCR may be needed for scanned resumes.
How do I prevent bias in automated resume screening? Keep scoring criteria tied strictly to job-relevant skills and experience stated in the description, avoid including demographic signals in prompts, and always have a human review flagged or borderline candidates before rejection.
Do I need an Anthropic API account to build this? No — if you already have Claude access through a subscription, you can expose it as a standard HTTPS API with SubToAPI instead of setting up separate API billing. Sign up at /signup to get a key and start calling /v1/messages directly.