Drop in an invoice, contract, or receipt (PDF / image / plain text) and Claude pulls out the doc number, both parties, amounts, tax, and line items — you review and correct on the web dashboard, then export straight to Excel or CSV for finance.
One command to deploy, SQLite out of the box, and a full demo mode so you can try the whole flow without an API key.
Every month, e-commerce shops and small businesses re-type a pile of vendor invoices, platform statements, and contracts into spreadsheets — manual entry is slow, numbers get mistyped, and outsourcing the data entry means handing sensitive documents to a third party. DocPilot compresses that into "upload → glance → export":
- Any format, straight upload — PDFs get their text extracted, images go through Claude vision, plain text is read as-is. No manual conversion step first.
- Structured extraction, not a summary — Claude's structured output returns fields against a strict schema, not a paragraph the model decided to write.
- A human stays in the loop — every field is editable right on the page. AI handles 95% of the typing; a person just double-checks the numbers that matter.
- One-click export — a two-sheet Excel workbook (documents + line items) or plain CSV, ready to hand to finance or import into an ERP.
- Duplicate detection built in — the same invoice number from the same vendor uploaded a second time gets flagged automatically, and the export carries that column too. Double payment is the single most expensive mistake in this workflow, and it's nearly invisible to the human eye.
- Failures stay visible — a scanned PDF with no extractable text, or an API error, still gets written to the database and shown in red on the dashboard. Nothing gets silently dropped.
- Demo mode — with no API key configured, DocPilot switches to a built-in deterministic fake extractor so you can try the entire flow offline before spending a cent.
After extraction, hit "Review" — every field is editable, with the line items shown alongside for reference. AI does 95% of the data entry; a person just confirms the amounts and names are right. That's an order of magnitude faster than re-typing everything by hand, and a lot safer than trusting the model blindly.
git clone https://github.com/LuciferLiu/docpilot.git
cd docpilot
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in ANTHROPIC_API_KEY; leave it blank for demo mode
uvicorn app.main:app --reloadWant to see it in action first? Generate a batch of demo data (no network required):
python seed_demo.pycp .env.example .env
docker compose up -dEverything is controlled through environment variables — see .env.example:
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
empty | Leave blank to run fully offline in demo mode with the built-in fake extractor |
CLAUDE_MODEL |
claude-opus-5 |
Model used for extraction — swap it to fit your budget |
DATABASE_URL |
sqlite:///./docpilot.db |
Point it at a Postgres connection string to switch databases |
MAX_UPLOAD_MB |
15 |
Per-file upload size limit |
app/
├── main.py FastAPI routes + application lifecycle
├── extractor.py Core: file parsing → Claude structured extraction → demo-mode fallback
├── exporter.py openpyxl export to Excel / CSV
├── models.py SQLAlchemy data models
├── schemas.py Pydantic request/response validation + extraction schema
├── config.py Environment variable configuration
├── db.py Database session
└── static/
└── index.html Dashboard (vanilla JS, zero build step)
There are four deliberate trade-offs baked into this design:
Extraction results live in one JSON column, not a normalized fields table. Invoices and contracts naturally have different field sets — forcing them into a rigid ExtractedField key-value table would only make querying and manual correction harder. One document, one row, one JSON blob for fields, with the shape enforced by a single Pydantic schema. Claude's structured output and a human's manual edit go through the exact same shape.
Failures get written to the database too. A scanned PDF with no extractable text, a model refusal, a dropped API connection — all of it lands in Document.error and shows up red on the dashboard. The single most dangerous failure mode in document processing is "one invoice quietly went missing and nobody noticed." Surfacing failure explicitly matters more than pretending everything succeeded.
It has to run without an API key. When ANTHROPIC_API_KEY is blank, DocPilot switches to a deterministic fake extractor: it generates plausible fake data from the filename and content, and the same file always produces the same result. Demos, local dev, and CI don't need network access or a bill — a prospective customer can see the full flow before deciding to wire in a real key.
Duplicate detection only flags, it never auto-deletes. Same vendor plus same document number is treated as a duplicate; the earliest upload is kept as the "original" and the rest get labeled "possible duplicate #N." Deciding which copy is actually void, or whether it's a legitimate reissued credit note, needs context the system doesn't have — so it doesn't make that call for finance. It only guarantees the situation won't go unnoticed.
Once the server is running, interactive docs live at /docs and can be tried straight from the browser:
| Method | Path | Description |
|---|---|---|
GET |
/api/documents |
List documents, including extracted fields and failure reasons |
POST |
/api/documents |
Upload a document (multipart; extraction runs automatically) |
GET |
/api/documents/{id} |
Get a single document's detail |
PATCH |
/api/documents/{id} |
Manually correct fields / change the document type |
DELETE |
/api/documents/{id} |
Delete a document |
POST |
/api/documents/{id}/reextract |
Re-run extraction on the stored text |
GET |
/api/export?format=xlsx|csv |
Export all documents (Excel includes a line-item sheet and duplicate flags) |
GET |
/api/stats |
Dashboard summary metrics |
Upload example:
curl -X POST http://localhost:8000/api/documents \
-F "file=@invoice_202606.pdf"Export to Excel:
curl -o documents_export.xlsx "http://localhost:8000/api/export?format=xlsx"Invoices / receipts: document number, date, seller, buyer, amount, tax, total, currency, line items. Contracts: contract number, signing date, party A, party B, contract amount, term.
Fields that can't be identified are left blank rather than guessed — it's better to make a person fill in one cell than hand finance a wrong number. The field schema lives in ExtractionResult inside app/schemas.py; adding a field only means touching that one class.
- Once you wire in a real API key, document content is sent to Anthropic's Claude API for extraction — make sure that fits your organization's data compliance requirements. Demo mode never touches the network.
- Original files aren't stored on disk; only the first 2,000 characters of extracted text are kept for review reference, and image-based documents need to be re-uploaded to re-extract.
- Scanned PDFs (pure images) have no extractable text — they'll be flagged as failed with a prompt to re-upload as an image, which routes through the vision pipeline.
MIT


