# How ATS Verification Extracts and Validates PDF Text Layers with pdftotext: A 3-Step Implementation

> Learn how ATS verification extracts and validates PDF text layers using pdftotext. Discover a 3-step implementation focusing on layout, whitespace normalization, and essential text checks.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-07-19

---

**The ATS verification utility in the MadsLorentzen/ai-job-search repository validates PDF text layers by invoking `pdftotext` with the `-layout` flag, normalizing whitespace, and enforcing checks for minimum character counts and required text substrings.**

The `ai-job-search` repository provides a robust ATS (Applicant Tracking System) verification tool that ensures generated PDFs contain machine-readable text layers essential for automated resume parsing. Located in [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py), this utility leverages Poppler utilities—specifically `pdftotext` and `pdfinfo`—to validate that PDF documents meet specific content and structure requirements before submission to ATS platforms.

## The Three-Step Verification Pipeline

The verification process implemented in [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py) follows a strict three-step pipeline to guarantee PDF accessibility. Each step utilizes external Poppler command-line tools invoked through a unified helper function.

### Step 1: Page Count Validation with pdfinfo

First, the script validates document length using the external **`pdfinfo`** command. The [`run_tool`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L15-L22) helper function executes this binary and captures its output. The [`parse_page_count`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L33-L38) function then extracts the integer value following the `Pages:` line. If the caller provides an `expected_pages` argument, the actual count is compared against this value, and a [`VerificationError`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L11-L13) is raised immediately upon mismatch.

### Step 2: Text Layer Extraction Using pdftotext

For ATS readability, the core extraction relies on **`pdftotext`** executed with the `-layout` flag. This flag preserves the physical layout structure, which improves parsing accuracy for resume formats. The [`run_tool`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L15-L22) function invokes `pdftotext -layout <pdf> -`, outputting the raw text stream to stdout. This approach ensures that even complex multi-column PDFs yield coherent text extraction suitable for automated screening systems.

### Step 3: Content Validation and Normalization

Once extracted, the raw text undergoes processing through [`normalize_text`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L40-L42), which collapses consecutive whitespace characters into single spaces. This normalization makes string matching resilient against layout variations.

The validation then performs two critical checks:

- **Minimum character verification**: The normalized text length is compared against the `min_chars` parameter (defaulting to 1). If the extracted content falls short, a [`VerificationError`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L59-L62) signals that the PDF lacks a proper searchable text layer.
- **Required text verification**: When provided via the `required_text` parameter or `--contains` CLI flag, each specified string is normalized and verified as a substring of the extracted content. Missing entries trigger a [`VerificationError`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L64-L66).

## Error Handling and Reporting

All validation failures funnel through the custom [`VerificationError`](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L11-L13) exception class. The `main` entry point catches these exceptions at lines [94-96](https://github.com/MadsLorentzen/ai-job-search/blob/master/tools/verify_pdf.py#L94-L96), reporting failures to `stderr` with descriptive messages. Successful verification prints a concise confirmation message: `Verified <pdf>`.

## Command-Line and Programmatic Usage

The tool supports both CLI invocation and direct Python integration.

Command-line usage allows quick verification with page counts, character minimums, and required text strings:

```bash
python -m tools.verify_pdf \
    resume.pdf \
    --pages 2 \
    --min-chars 100 \
    --contains "Professional Experience" \
    --contains "your.email@example.com"

```

For integration into larger workflows, import the `verify_pdf` function directly:

```python
from pathlib import Path
from tools.verify_pdf import verify_pdf, VerificationError

pdf_path = Path("resume.pdf")

try:
    verify_pdf(
        pdf_path,
        expected_pages=2,
        min_chars=100,
        required_text=("Professional Experience", "your.email@example.com")
    )
    print("PDF passed ATS verification")
except VerificationError as exc:
    print(f"Verification failed: {exc}")

```

## Testing the Verification Logic

The test suite in [[`tests/test_verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_verify_pdf.py)](https://github.com/MadsLorentzen/ai-job-search/blob/master/tests/test_verify_pdf.py) mocks the external `pdfinfo` and `pdftotext` commands to validate each logic branch. These unit tests ensure the verification pipeline behaves correctly regardless of whether the underlying Poppler utilities are installed on the system, covering edge cases such as missing text layers and page count mismatches.

## Summary

- The [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py) script validates PDFs for ATS compatibility using Poppler utilities `pdfinfo` and `pdftotext`.
- **Page count validation** uses `pdfinfo` output parsed by `parse_page_count` to verify document length against `expected_pages`.
- **Text extraction** runs `pdftotext -layout` to preserve formatting while extracting searchable text layers.
- **Content validation** enforces minimum character requirements via `min_chars` and verifies the presence of required text substrings after whitespace normalization.
- The custom `VerificationError` exception provides unified error handling for all validation failures, caught and reported by the main entry point.
- Comprehensive unit tests in [`tests/test_verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tests/test_verify_pdf.py) mock external dependencies to ensure reliability without requiring actual PDF tool installations.

## Frequently Asked Questions

### What is pdftotext and why is it used for ATS verification?

`pdftotext` is a command-line utility from the Poppler suite that converts PDF documents to plain text. The `ai-job-search` repository uses it specifically because it reliably extracts hidden text layers from PDFs, which is essential for verifying that resumes remain machine-readable by Applicant Tracking Systems that cannot parse image-based or scanned documents.

### How does the tool handle PDFs with missing text layers?

If a PDF contains no extractable text layer, `pdftotext` returns minimal or empty output. The `normalize_text` function processes this result, and the subsequent `min_chars` check (defaulting to 1 character) fails, raising a `VerificationError`. This ensures that image-only PDFs or improperly exported documents are flagged before submission to ATS platforms.

### Can I verify multiple PDFs at once using the CLI?

The current implementation in [`tools/verify_pdf.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/verify_pdf.py) processes a single PDF path per invocation. To batch-process multiple files, you would need to wrap the CLI call in a shell loop or script that iterates through your PDF directory, invoking `python -m tools.verify_pdf` for each document individually.

### What happens if pdfinfo or pdftotext is not installed?

The `run_tool` helper function executes these external commands via subprocess. If either `pdfinfo` or `pdftotext` is missing from the system PATH, the subprocess call will fail and raise an exception. The test suite mitigates this dependency by mocking these external tools, but production usage requires installing the Poppler utilities package (typically `poppler-utils` on Linux/Unix systems).