Why
AI agents are impressive — until a real-world PDF hits them: a scanned invoice, a receipt, a messy form. They hallucinate, return half-broken data, or fail silently.
The hard part was never the model. It's reliability on messy inputs. That's what docapi is.
Every response is validated against your schema. If it can't match, you get a structured error — never silently-wrong data.
What it does
messy file ──► ingest → extract → understand → validate → normalize → ground ──► schema-shaped JSON
A real example — an Indian receipt with a DD-MM-YYYY date that trips up language models:
The model alone reads
26-05-2025as the year 2605. docapi fixes it deterministically — dates are a solved problem in code, so we don't make the model guess. Reliability is the product.
1. Install
You need Python 3.11+. uv is recommended (fast), but plain pip works too.
git clone https://github.com/Waterbottles792/docapi.git
cd docapi
# with uv (recommended)
uv venv
uv pip install -e .
# …or with plain pip
python -m venv .venv && source .venv/bin/activate
pip install -e .
Want to verify it installed? Run the offline demo — it needs no model and no key:
uv run python scripts/demo.py --stub # (or: python scripts/demo.py --stub)
2. Pick how the "understand" step runs
docapi does everything deterministically except one step — understand, where a model maps
messy text onto your fields. That single step is swappable behind one env var, LLM_PROVIDER:
LLM_PROVIDER |
Needs a key? | Cost | Best for |
|---|---|---|---|
stub (default) |
no | $0 | kicking the tires — returns canned data, proves the plumbing |
ollama |
no | $0 | real extraction, 100% local & private |
anthropic |
yes (your Claude key) | paid | speed + quality, big context, long documents |
Choose your path below. 👇
🟢 If you DON'T have an API key — run it free & local with Ollama
This is the recommended way to use docapi for free. It runs a small model on your own machine — no cloud, no cost, nothing leaves your computer.
# 1. Install Ollama: https://ollama.com/download
# 2. Pull a small model (~2 GB, one-time)
ollama pull llama3.2
# 3. Tell docapi to use it
export LLM_PROVIDER=ollama
export LLM_MODEL=llama3.2
That's it — every command below now does real extraction, locally and free. docapi hands the model a JSON Schema so it's constrained to emit matching JSON; combined with a corrective retry and deterministic date repair, even a 3B model extracts invoices reliably.
Long documents on a small local model can be slow (it chunks them to fit). For multi-page docs at speed, use the Claude path below.
🔵 If you HAVE a Claude (Anthropic) API key — use it for speed + quality
# 1. Install the optional Anthropic dependency
uv pip install -e ".[anthropic]" # (or: pip install -e ".[anthropic]")
# 2. Point docapi at Claude with your key
export LLM_PROVIDER=anthropic
export ANTHROPIC_API_KEY=sk-ant-... # your key from console.anthropic.com
# optional: export LLM_MODEL=claude-haiku-4-5 (the default — cheapest & fast)
Claude's large context window means long documents extract in seconds with no chunking, at
higher accuracy. Your key is read from the environment and never leaves your machine. Haiku is
the cheapest model and plenty for most extraction; set LLM_MODEL to a Sonnet/Opus id for harder docs.
⚪ Just want to see it work? — the stub (no model, no key)
unset LLM_PROVIDER # or: export LLM_PROVIDER=stub
uv run python scripts/demo.py --stub
Returns realistic canned data instantly so you can see the full pipeline without installing anything.
3. Use it — four ways
All four run the exact same extract_to_schema core, so the reliability guarantees are identical.
a) Try it on your own PDF (CLI)
uv run python scripts/try_extract.py path/to/your.pdf
(Uses whatever LLM_PROVIDER you set above. Add --ollama to force the local model for one run.)
b) REST API
uv run uvicorn docapi.main:app --reload # interactive docs at http://127.0.0.1:8000/docs
curl -s -F '[email protected]' \
-F 'schema={"invoice_number":"string","invoice_date":"date","total_amount":"number"}' \
http://127.0.0.1:8000/v1/extract | jq
Response: { "data": {...}, "confidence": 0.97, "pages": 1, "warnings": [] } — or a structured
error if it couldn't produce schema-valid data.
c) MCP tool (let an agent call it directly)
The same pipeline is exposed as an MCP tool, so an AI agent can extract documents with no HTTP glue. Point Claude Desktop (or any MCP client) at it:
// claude_desktop_config.json
{
"mcpServers": {
"docapi": {
"command": "uv",
"args": ["run", "python", "-m", "docapi.mcp_server"],
"cwd": "/absolute/path/to/docapi",
"env": { "LLM_PROVIDER": "ollama", "LLM_MODEL": "llama3.2" }
}
}
}
Then just ask: "Extract the invoice number, date, and total from ~/receipt.pdf." The agent calls
the extract_document tool and gets back schema-valid JSON. (Swap the env block for your
Anthropic key to use Claude instead.)
d) In Python
from docapi.core.pipeline import extract_to_schema
with open("invoice.pdf", "rb") as f:
result = extract_to_schema(
f.read(), "invoice.pdf",
{"invoice_number": "string", "invoice_date": "date", "total_amount": "number"},
)
result.data # → schema-valid dict
result.confidence # → 0.0–1.0
result.warnings # → e.g. ungrounded (possibly hallucinated) fields
The schema format
Map field names to types. Append ? for optional. Arrays are [ {item} ]; nest objects freely.
{
"vendor_name": "string",
"document_date": "date",
"total": "number",
"tax_id": "string?",
"line_items": [ { "description": "string", "amount": "number" } ]
}
Supported scalars: string, number, integer, boolean, date (ISO-8601).
Reliability engine
- ✅ Schema validation on every output — never returns the wrong shape
- ✅ Grounding check — every extracted string is verified against the source text; anything the model invented is flagged and confidence drops (catches hallucination)
- ✅ Long-document chunking — big docs are split into windows that fit a small model's context, extracted independently, then merged (lists unioned, booleans OR-ed); no more silent truncation of late pages
- ✅ One corrective retry feeding errors back to the model
- ✅ Deterministic date normalization (no more
2605bugs) - ✅ Structured errors with stable
type+ human message - ✅ Confidence signal on every success
- ⏳ OCR fallback for scans — next
Does it actually work? (eval harness)
Tests prove the plumbing; the eval harness measures whether the pipeline returns the correct values on real documents — scoring each output field-by-field against a known-correct answer.
LLM_PROVIDER=ollama LLM_MODEL=llama3.2 uv run python scripts/eval.py
# ✓ acme_invoice 3/3 fields
# ✓ euro_invoice 3/3 fields (slash-format date)
# ✓ nptel_receipt 3/3 fields (the 2605 date case)
# ✓ resume 13/13 fields (lists + nested objects, not an invoice)
# field accuracy: 100% (22/22 across 4 docs)
On the starter set, 100% field accuracy (22/22) on a free 3B local model — across flat
invoices and a résumé with skill lists, nested job history, and an experience count
inferred from prose. That's a small, directional set — see evals/ to grow it.
The harness doubles as a regression gate: --threshold 0.85 makes it exit non-zero if
accuracy slips.
Develop / contribute
uv pip install -e ".[dev]"
uv run pytest # 80 tests
uv run ruff check .
uv run mypy src
LLM_PROVIDER=ollama uv run python scripts/eval.py # accuracy on real docs
Issues and PRs welcome. See SPEC.md for the full v1 spec.
Status & roadmap
Working end-to-end today: core pipeline + REST API + MCP server + reliability layer + eval harness + 80 tests.
- Extract-to-schema pipeline (REST
POST /v1/extract) - Free local model (Ollama) + deterministic date repair
- MCP
extract_documenttool (same pipeline, agent-native) - Eval harness with field-level accuracy + regression gate
- Grounding (hallucination) check + long-document chunking
- Anthropic (Claude) provider for the paid, high-quality path
- OCR fallback for scanned docs
- Hosted version: accounts, API keys, usage metering
License & commercial use
Open source under the Apache-2.0 license — use it, self-host it, build on it.
A managed, hosted version is on the way: an API key instead of running a model yourself, plus auth, usage metering, and scale — the same open core you see here is the engine behind it.
👉 Want early access? Join the waitlist at docapi-one.vercel.app — or open an issue / reach out.
No comments yet
Be the first to share your take.