# How the ATS Checking Feature Works: PDF Text Extraction and Validation

> Learn how the ATS checking feature extracts PDF text, validates readability, contact details, and keyword coverage against job postings. Ensure your resume passes ATS scans.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: deep-dive
- Published: 2026-07-09

---

**The ATS checking feature extracts the text layer from compiled PDF résumés using `pdftotext` and validates mechanical readability, contact detail accessibility, and keyword coverage against job postings before final submission.**

The ATS (Applicant Tracking System) checking feature is a critical validation step in the `MadsLorentzen/ai-job-search` repository's `/apply` workflow. It ensures that every generated résumé is parseable by automated hiring systems while maintaining the visual design integrity of the LaTeX template. This process runs automatically after PDF compilation and before the final draft is presented to the user.

## What Triggers the ATS Check?

The ATS checking feature activates automatically when the `/apply` command finishes generating `cv/main_<company>.pdf`. According to the source code in [`tools/security_guards.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/security_guards.py) (line 40), the system first detects the optional `pdftotext` binary from the Poppler suite and whitelists it for safe execution.

If `pdftotext` is present on the system, the workflow invokes it immediately; otherwise, it prints a warning to the Step 6 report and falls back to a purely visual keyword review. This behavior is documented in [`SETUP.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/SETUP.md) under the section "Optional: `pdftotext` (for the ATS check)" and in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md) under the "Availability check" section.

## Extracting the PDF Text Layer

When the binary is available, the ATS checking feature executes the following command to extract the résumé's text layer:

```bash
cd cv && pdftotext -layout main_<company>.pdf main_<company>.txt

```

This command is documented in [`.claude/skills/job-application-assistant/05-cv-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/05-cv-templates.md) (lines 182-185). The `-layout` flag preserves column positions, ensuring that the extracted text mirrors the visual ordering as closely as possible. This preservation is crucial for validating that multi-column layouts or sidebars do not scramble the reading order that ATS parsers expect.

## Mechanical Validation Checks

The parser inspects the resulting `.txt` file for three core ATS-relevant properties, as defined in [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md) (lines 130-135) and [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md) (lines 225-230):

### Literal Contact Details

Email and phone numbers must appear as plain text (e.g., `john.doe@example.com`). The validation fails if contact information is embedded only as icons without text labels (such as `MOBILE-ALT` symbols), since ATS parsers ignore graphical elements.

### Glyph Integrity

The extracted text must be Unicode-clean without garbled glyphs. The system checks for `(cid:...)` placeholders or `` symbols, which indicate fonts lacking proper Unicode mapping. These garbage strings render the résumé unreadable to ATS software.

### Reading Order

Single-column layouts must yield a top-to-bottom ordering that matches the visual résumé. Multi-column designs or sidebar-heavy templates can cause extraction order to mismatch visual order, confusing parser algorithms.

If any mechanical check fails, the system flags the template and suggests concrete LaTeX fixes, such as adding `\needspace{5\baselineskip}` before a `\cventry` command or moving contact data out of pure icon fonts.

## Keyword Coverage Assessment

After mechanical validation passes, the ATS checking feature calculates **keyword coverage** through a three-step process:

1. **Harvesting**: The job posting's required and preferred terms are extracted by the `/apply` workflow (see the "Job-fit evaluation" section in [`README.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/README.md), lines 197-215).
2. **Matching**: The extracted text is scanned for exact matches (case-insensitive). Synonyms are not counted unless they appear verbatim in the posting, as many ATS parsers perform literal matching rather than semantic analysis.
3. **Reporting**: A coverage score is generated, and missing required keywords are listed as **gaps**. The "Honesty rule" enforced by the system prevents the assistant from fabricating keywords that do not appear in the candidate's actual experience.

## Integration in the /apply Workflow

The ATS check is the **5d** sub-step of the overall workflow, as documented in [`README.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/README.md) (lines 215-218) under "How `/apply` works". It runs *after* PDF compilation and *before* the final draft is saved, ensuring that every submitted résumé is ATS-friendly by default.

If `pdftotext` is missing, the workflow adds a one-line warning to the report and performs the keyword review manually against the visual PDF. When text extraction succeeds, the report contains:
- Pass/fail status for each mechanical check
- A table of matched versus missing keywords
- Suggested LaTeX edits to fix any detected ATS problems

## Running the ATS Check Manually

You can replicate the ATS checking feature locally using the following commands:

```bash

# 1. Compile the LaTeX CV (already done by /apply)

lualatex -output-directory=cv cv/main_awesomecompany.tex

# 2. Run the ATS text extraction (optional, only if pdftotext is installed)

if command -v pdftotext >/dev/null; then
    cd cv
    pdftotext -layout main_awesomecompany.pdf main_awesomecompany.txt
    
    # 3. Simple sanity checks (illustrative)

    grep -iE 'john\.doe@|john\.doe' main_awesomecompany.txt && echo "Contact OK"
    grep -i 'cid:' main_awesomecompany.txt && echo "Garbage glyphs found!"
    
    # 4. Keyword check (example with a posting keyword list)

    keywords=("Python" "Machine Learning" "Docker")
    for kw in "${keywords[@]}"; do
        if grep -iq "$kw" main_awesomecompany.txt; then
            echo "✔ $kw"
        else
            echo "✘ $kw (missing)"
        fi
    done
else
    echo "pdftotext not installed – ATS check skipped (visual review only)."
fi

```

The real `/apply` implementation runs these same commands internally and formats the results into a structured report for the user.

## Summary

- The ATS checking feature validates the text layer of PDF résumés using the `pdftotext` binary, which is whitelisted in [`tools/security_guards.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/security_guards.py).
- It performs three mechanical validations: literal contact details, Unicode glyph integrity, and correct reading order.
- Keyword coverage is calculated using exact case-insensitive matches against harvested job posting terms.
- The check runs as step **5d** in the `/apply` workflow, with graceful degradation to visual review if `pdftotext` is unavailable.

## Frequently Asked Questions

### What happens if pdftotext is not installed on my system?

If the `pdftotext` binary is missing, the ATS checking feature prints a warning to the Step 6 report and falls back to a manual visual keyword review against the PDF. The workflow continues but cannot validate mechanical ATS compatibility such as glyph integrity or text extraction order.

### Why does the ATS check require exact keyword matches instead of synonyms?

The system requires exact matches because most ATS parsers perform literal string matching rather than semantic analysis. According to the implementation in [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md), synonyms are only counted if they appear verbatim in the job posting, ensuring that the validation mirrors real-world ATS behavior.

### How does the -layout flag in pdftotext improve ATS compatibility?

The `-layout` flag preserves the spatial positioning of text columns during extraction, as documented in [`05-cv-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/05-cv-templates.md). This ensures that the extracted `.txt` file maintains the same reading order as the visual résumé, preventing multi-column layouts from scrambling into incoherent text sequences that confuse ATS parsers.

### Where can I find the whitelisting configuration for the pdftotext binary?

The `pdftotext` binary is whitelisted in [`tools/security_guards.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/security_guards.py) at line 40. This security guard allows the `/apply` workflow to safely execute the binary during the ATS validation step without triggering execution restrictions.