odysseus-dev/odysseus · info · HTTPException
Document is not a PDF — no pdf_source marker found
Error message
Document is not a PDF — no pdf_source marker found
What it means
400 from POST /api/document/{doc_id}/extract-pdf-text: the document exists and is owned by the caller, but its current_content contains no pdf_source marker that find_source_upload_id can parse. The endpoint only works on documents originally imported from a PDF; text-native or otherwise-created docs have no linked upload.
Source
Thrown at routes/document/document_routes.py:523
Lets the AI see PDF contents for old docs that were imported before
text extraction was wired, plus for scanned/image-only PDFs where the
VL model picks up text the basic pypdf path missed."""
import re
from src.document_processor import _process_pdf, strip_pdf_content_marker
from src.pdf_form_doc import find_source_upload_id
user = get_current_user(request)
db = SessionLocal()
try:
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:
raise HTTPException(404, "Document not found")
_verify_doc_owner(db, doc, user)
content = doc.current_content or ""
upload_id = find_source_upload_id(content)
if not upload_id:
raise HTTPException(400, "Document is not a PDF — no pdf_source marker found")
pdf_path = _locate_current_user_upload(request, upload_id, user)
if not pdf_path:
raise HTTPException(404, "Source PDF could not be located")
try:
body_text = strip_pdf_content_marker(_process_pdf(pdf_path, owner=user))
except Exception as e:
logger.error(f"extract_pdf_text failed for {pdf_path}: {e}")
raise HTTPException(500, f"Extraction failed: {e}")
if not body_text:
return {"ok": True, "id": doc_id, "extracted": False, "reason": "No readable content"}
# Preserve everything up through the title (front-matter marker +
# first H1) and replace the rest with the freshly extracted text.
head_re = re.compile(r'^(<!--[^>]+-->\s*\n+#[^\n]*\n+)', re.MULTILINE)
head_match = head_re.match(content)View on GitHub (pinned to f9235ebbf1)
Solutions
- Only offer/attempt extraction for docs whose content contains the pdf_source marker (or that were created by import-pdf).
- For legacy PDF docs without the marker, re-import the PDF to create a properly linked doc.
- If content edits must preserve provenance, keep the marker intact when rewriting doc content.
- Handle the 400 as 'not applicable' in automation rather than as a failure.
Example fix
// before
await api.post(`/api/document/${doc.id}/extract-pdf-text`); // 400 on text docs
// after
const isPdfDoc = /<!--\s*pdf_source/.test(doc.current_content);
if (isPdfDoc) await api.post(`/api/document/${doc.id}/extract-pdf-text`); Defensive patterns
Strategy: type-guard
Validate before calling
const isPdfDoc = /<!--\s*pdf_source[^>]*-->/.test(doc.current_content ?? ''); if (!isPdfDoc) return; // skip extraction for non-PDF docs
Type guard
function isPdfBackedDoc(doc: { current_content?: string | null }): boolean {
return /pdf_source/.test(doc.current_content ?? '');
} Try / catch
try { await api.post(`/api/document/${id}/extract-pdf-text`); }
catch (e) { if (e.status === 400 && /not a PDF/.test(e.message)) return; /* expected for text docs */ throw e; } Prevention
- Gate the extract action on the pdf_source marker in the UI
- Preserve the marker when programmatically rewriting doc content
- Treat this 400 as 'not applicable', never as a retryable failure
When it happens
Trigger: Running extraction on a markdown/text document created directly (POST /api/documents); a PDF-imported doc whose content was later fully overwritten by an edit that dropped the marker; very old docs imported before the pdf_source marker convention existed.
Common situations: Bulk 'extract all' scripts hitting mixed libraries; UI offering the extract action on every doc regardless of origin; content-rewrite tools stripping HTML comments (the marker is an HTML comment) during transforms.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/58a9561a21868185.
Report an issue: GitHub.