Build an AI resume screener
Score and summarise Northwind applicants against a role spec, into a hiring sheet.
Published Aug 15, 2025
A Northwind job opening pulls in fifty CVs, and the first pass through them is the same tedious job every time: open each PDF, hold the role spec in your head, decide whether the candidate is worth a closer look. By CV thirty the standard has drifted, and good applicants get missed because attention ran out.
This script does the first pass consistently. It reads every CV in a Drive folder, scores each one against the role spec with Claude, and writes the score plus a one-line summary into a hiring sheet. You still make every real decision — but you make it from a ranked shortlist instead of a folder of unopened PDFs.
What you’ll need
- A Drive folder containing the applicant CVs as PDF files.
- A Google Sheet to collect the results — you’ll put its ID in the config.
- An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - A short role spec — a paragraph of must-haves and nice-to-haves — that you pass to the function.
The script
// The spreadsheet that collects screening results.
const APPLICANTS_SHEET_ID = '1abcApplicantsId';
// How much of each CV to send. Enough for a one-page resume; keeps
// the prompt within a sensible token budget.
const MAX_RESUME_CHARS = 4000;
/**
* Screens every PDF CV in a Drive folder against a role spec and
* writes a score and summary for each into the applicants sheet.
*
* @param {string} folderId - The Drive folder holding the CVs.
* @param {string} roleSpec - A short description of the role's requirements.
*/
function screenResumes(folderId, roleSpec) {
// 1. Get an iterator over the PDF files in the folder.
const files = DriveApp.getFolderById(folderId)
.getFilesByType('application/pdf');
if (!files.hasNext()) {
Logger.log('No PDF CVs in that folder — nothing to screen.');
return;
}
const sheet = SpreadsheetApp.openById(APPLICANTS_SHEET_ID).getSheets()[0];
// 2. Add a header row if the sheet is empty.
if (sheet.getLastRow() === 0) {
sheet.appendRow(['CV', 'Score', 'Summary', 'Link']);
}
let screened = 0;
while (files.hasNext()) {
const f = files.next();
// 3. Read the CV text and trim it to a sensible length.
const text = f.getBlob().getDataAsString().slice(0, MAX_RESUME_CHARS);
// 4. Ask Claude to score the candidate, pinned to strict JSON.
const prompt =
'Score this candidate from 0-100 against this role:\n' + roleSpec +
'\n\nResume:\n' + text +
'\n\nReturn ONLY a JSON object — no prose, no markdown — in this shape: ' +
'{"score": number, "summary": "one sentence"}.';
const out = JSON.parse(stripFences(callClaude(prompt)));
// 5. Append one row per candidate to the hiring sheet.
sheet.appendRow([f.getName(), out.score, out.summary, f.getUrl()]);
screened++;
}
Logger.log('Screened ' + screened + ' CV(s).');
}
/**
* Claude occasionally wraps JSON in a ```json code fence. Strip it so
* JSON.parse never chokes on the markdown.
*/
function stripFences(text) {
return text.replace(/```(?:json)?/g, '').trim();
}
/**
* Minimal Anthropic API call. The key lives in Script Properties — it
* is never pasted into the code.
*/
function callClaude(prompt, model = 'claude-sonnet-4-6', maxTokens = 300) {
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model,
max_tokens: maxTokens,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
screenResumesopens the Drive folder and gets an iterator over its PDF files. If there are none, it logs a message and stops.- It opens the applicants sheet and adds a header row if the sheet is still empty, so the first run produces a labelled table.
- For each CV it reads the file’s text and trims it to
MAX_RESUME_CHARS— enough for a one-page resume without overspending tokens. - It builds a prompt that includes the role spec and the CV text, pinned to a
strict JSON object with a numeric
scoreand a one-sentencesummary. - It calls Claude Sonnet — judging a candidate against a spec needs reasoning,
so the stronger model earns its cost here.
stripFencesremoves any code fence, thenJSON.parseturns the reply into a real object. - It appends a row per candidate with the file name, score, summary, and a link straight back to the CV in Drive.
Example run
Call it from the editor with a folder ID and a role spec:
screenResumes('1AbcFolderId',
'Customer support lead. Must-have: 3+ years in B2B SaaS support, ' +
'team management. Nice-to-have: knowledge-base writing, multilingual.');
After a run over four CVs, the applicants sheet holds a ranked-ready table:
| CV | Score | Summary | Link |
|---|---|---|---|
| jordan-blake.pdf | 88 | Six years B2B SaaS support, led a team of eight — strong fit. | open |
| priya-nair.pdf | 74 | Solid support background but limited management experience. | open |
| sam-okafor.pdf | 61 | Strong writer, mostly consumer support, no team lead history. | open |
| alex-kim.pdf | 38 | Career in field sales — little overlap with the support role. | open |
Sort the sheet by score and the shortlist is the top of the table.
Run it
This is an on-demand job — you run it when applications close, not on a schedule:
- In the Apps Script editor, write a small wrapper that calls
screenResumeswith your folder ID and role spec, then select it and click Run. - Approve the authorisation prompt the first time.
- Open the applicants sheet, sort by the
Scorecolumn, and start reading from the top.
Watch out for
getDataAsStringreads a PDF as raw bytes. For text-based PDFs this usually yields readable text, but a scanned or image-only CV will come through as garbage — those candidates need OCR or a manual read.- Scores are a guide, not a verdict. Always read the CVs near your cut-off line yourself; a strong candidate with an unusual background can score low.
- Be deliberate about fairness. An AI screener can carry bias from a vague role spec — keep the spec concrete and skills-focused, and never let the score be the only thing a candidate is judged on.
appendRowruns once per CV. A folder of fifty PDFs means fifty API calls plus fifty writes, which can approach the six-minute limit — split a large batch across folders, or collect rows and write them with onesetValues.- The script re-screens every PDF in the folder on each run. Move screened CVs to a “done” subfolder, or check the sheet for the file name first, to avoid duplicate rows.
Related
Build an AI keyword-clustering tool
Group Northwind's tracked search terms into topic clusters — for SEO content planning.
Updated Feb 19, 2026
Build an AI customer-churn predictor
Flag at-risk Northwind accounts from behavioural signals — usage, support tickets, billing.
Updated Feb 15, 2026
Build a context-aware AI data validator
Catch values that look wrong in context — '£10' for a Northwind retainer is suspicious.
Updated Feb 7, 2026
Auto-categorize a photo library
Tag Northwind Drive images by visual content — product, team, event, behind-the-scenes.
Updated Feb 3, 2026
Build an AI bug-triage system
Categorise and prioritise Northwind's reported issues automatically — type, severity, owner.
Updated Jan 22, 2026