srbhr/Resume-Matcher · error · HTTPException
Failed to parse document. Please ensure it's a valid PDF or
Error message
Failed to parse document. Please ensure it's a valid PDF or DOCX file.
What it means
When parse_document (markitdown-based PDF/DOC → Markdown conversion) raises for any reason, upload_resume logs the real exception server-side and re-raises HTTPException 422 with this generic, action-oriented detail. It is the standard backend pattern: log details, return a generic client message.
Source
Thrown at apps/backend/app/routers/resumes.py:660
)
# Read and validate size
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=413,
detail=f"File too large. Maximum size: {MAX_FILE_SIZE // (1024 * 1024)}MB",
)
if len(content) == 0:
raise HTTPException(status_code=400, detail="Empty file")
# Convert to markdown
try:
markdown_content = await parse_document(content, file.filename or "resume.pdf")
except Exception as e:
logger.error(f"Document parsing failed: {e}")
raise HTTPException(
status_code=422,
detail="Failed to parse document. Please ensure it's a valid PDF or DOCX file.",
)
# Validate extracted text is not empty (image-based PDFs / scanned documents)
if not markdown_content or not markdown_content.strip():
raise HTTPException(
status_code=422,
detail=(
"Could not extract text from the uploaded file. The document may be "
"image-based or scanned. Please upload a text-based PDF/DOCX with "
"selectable text, or run OCR first."
),
)
# Store in database first with "processing" status (atomic master assignment)
# original_markdown is preserved permanently for date reference even after
# builder saves overwrite `content` with JSON.View on GitHub (pinned to 116f9cc3b0)
Solutions
- Check the backend log 'Document parsing failed: <e>' for the real cause
- Open the file locally; if it prompts for a password, remove protection before uploading
- Re-export the document as a clean PDF or DOCX from the original application
- Verify markitdown and its PDF extras are installed at pinned versions (uv sync)
Example fix
// before: uploading a password-protected PDF POST /api/v1/resumes/upload file=protected.pdf -> 422 // after: strip protection first, then upload qpdf --decrypt protected.pdf resume.pdf && curl -F 'file=@resume.pdf;type=application/pdf' http://localhost:8000/api/v1/resumes/upload
Defensive patterns
Strategy: validation
Validate before calling
// client: sanity-check the file opens as a PDF before upload
const buf = new Uint8Array(await file.slice(0, 5).arrayBuffer());
const isPdf = String.fromCharCode(...buf) === '%PDF-';
if (file.name.endsWith('.pdf') && !isPdf) {
throw new Error('File is not a real PDF (missing %PDF- header) — re-export it');
} Try / catch
try {
await api.postForm('/resumes/upload', form);
} catch (e) {
if (e.response?.status === 422 && /Failed to parse document/.test(e.response?.data?.detail ?? '')) {
showToast('Could not read that document — remove password protection and re-export as PDF/DOCX');
}
} Prevention
- Remove password/DRM protection before uploading (qpdf --decrypt or print-to-PDF)
- Re-export from the source app instead of renaming other formats to .pdf
- Check the server log 'Document parsing failed' for the precise markitdown error
- Keep markitdown and PDF extras at pinned versions (uv sync) after upgrades
When it happens
Trigger: POST /api/v1/resumes/upload with a file that passes type/size checks but cannot be converted: corrupt or password-protected PDFs, DRM-protected files, malformed DOCX (renamed .txt or .html), or a markitdown/dependency failure on an exotic PDF structure.
Common situations: Password-protected PDFs; files renamed to .pdf but actually images/HTML; encrypted PDFs from secure portals; markitdown version issues after a dependency upgrade; corrupt uploads from flaky networks.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Could not extract text from the uploaded file. The document
- missing_placeholders
- Failed to download resume (status ${res.status}): ${text}
- Failed to download cover letter (status ${res.status}): ${te
- Playwright browser executable is missing, and no system Chro
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/b894ecbc92ba93f1.
Report an issue: GitHub.