← Blog

How to Build a Resume Parser with Claude

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

Building a resume parser with Claude means sending resume text (or a PDF) to the model with a defined JSON schema and letting Claude extract structured fields like name, skills, work history, and education — instead of writing brittle regex rules or training a custom NLP model. This approach works well because resumes come in wildly inconsistent formats, and Claude's language understanding handles that variance far better than pattern matching.

This guide walks through the actual implementation: how to structure the prompt, how to use tool calling to force structured output, how to handle PDFs and different file formats, and how to deal with edge cases like multi-column layouts or non-English resumes.

Why Claude Works Well for Resume Parsing

Traditional resume parsers rely on keyword matching and layout heuristics. They break constantly because resumes have no standard format — some use tables, some use columns, some use creative section headers like "Where I've Been" instead of "Experience."

Claude reads the resume the way a human recruiter would: it understands context, infers missing labels, and normalizes inconsistent date formats or job titles. The tradeoff is cost and latency per request compared to a rule-based parser, but for most hiring pipelines that process hundreds or thousands of resumes rather than millions, it's a reasonable trade for dramatically better accuracy.

Step 1: Get Resume Text Into a Usable Format

Claude needs text input. If resumes arrive as PDFs or Word docs, extract the text first using a library like pdf-parse (Node) or pypdf (Python). You don't need perfect formatting — Claude handles messy whitespace and broken line breaks reasonably well.

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

const buffer = fs.readFileSync("resume.pdf");
const data = await pdf(buffer);
const resumeText = data.text;

For scanned image PDFs, you'll need OCR first (Tesseract or a cloud OCR service) since Claude's text API doesn't read pixels — only text.

Step 2: Define a Strict Output Schema

The key to reliable parsing is not asking Claude to "extract the info" in free text. Use tool calling so the response is forced into a predictable JSON shape your application can consume directly.

{
  "name": "extract_resume",
  "description": "Extract structured candidate data from resume text",
  "input_schema": {
    "type": "object",
    "properties": {
      "full_name": { "type": "string" },
      "email": { "type": "string" },
      "phone": { "type": "string" },
      "skills": { "type": "array", "items": { "type": "string" } },
      "work_experience": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "company": { "type": "string" },
            "title": { "type": "string" },
            "start_date": { "type": "string" },
            "end_date": { "type": "string" },
            "description": { "type": "string" }
          },
          "required": ["company", "title"]
        }
      },
      "education": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "institution": { "type": "string" },
            "degree": { "type": "string" },
            "year": { "type": "string" }
          }
        }
      },
      "total_years_experience": { "type": "number" }
    },
    "required": ["full_name", "skills", "work_experience"]
  }
}

This schema is your contract. Claude will populate it field by field based on the resume text, and because it's a tool call rather than free-form text, you don't need to parse markdown or guess where JSON starts and ends. Read more on how this works in the tool use docs.

Step 3: Send the Request

If you're routing through SubToAPI, the request looks like any other application API call — no separate Claude account management, just your sub_live_ key.

const response = 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-5",
    max_tokens: 2048,
    tools: [resumeExtractionTool],
    tool_choice: { type: "tool", name: "extract_resume" },
    messages: [
      {
        role: "user",
        content: `Extract structured data from this resume:\n\n${resumeText}`
      }
    ]
  })
});

const data = await response.json();
const extracted = data.content.find(c => c.type === "tool_use").input;

Forcing tool_choice to the specific tool guarantees Claude doesn't respond with commentary — it goes straight to structured output. This is the single most important detail for production reliability.

Step 4: Handle Edge Cases

A few things come up constantly in real resume datasets:

Step 5: Validate and Store

Even with a tool schema, run a lightweight validation pass (Zod, Pydantic, or similar) before writing to your database. Claude is reliable but not infallible — an occasional malformed email or an empty skills array on a sparse resume is worth catching before it breaks downstream matching logic.

Scaling the Pipeline

If you're building this into a hiring platform or ATS, you'll likely process resumes in batches. A few practical notes:

FAQ

Can Claude parse resumes directly from PDF files without text extraction? No — Claude's Messages API works on text input, so you need to extract text from PDFs first using a library like pdf-parse or pypdf. Scanned/image-based PDFs require OCR before extraction.

How accurate is Claude at extracting work history dates? Very accurate when you explicitly request a normalized date format (e.g., YYYY-MM) in the prompt or schema description. Without that instruction, Claude may return dates in whatever format the resume used, which complicates downstream sorting.

Should I use tool calling or just ask Claude to return JSON in plain text? Use tool calling. It constrains the output to your schema and avoids the parsing failures that come from extracting JSON out of free-form text responses — see the messages and tools docs for implementation details.

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 →