docpipeline

Documents in, Markdown out —
and it tells you when it failed.

Every parsing engine is brilliant at one thing and bad at another. Rather than picking one and living with its weaknesses, this runs all of them and chooses per document — the fastest one that works, checked against a deterministic quality bar, with the score it was judged on returned alongside the Markdown.

The ladder

A parser is tried, its output is measured, and the result either stands or escalates. Nothing climbs a rung it does not need.

0local free Native text layer. Milliseconds.
1local_ocr free Rasterise and OCR the pages that need it. PDF only.
2vlm Per-page vision transcription, for scans OCR cannot read.

The vision tier needs two independent gates open: its config block enabled, and a rule raising max_tier to it. Neither alone is enough, and a request may only ever lower that ceiling, never raise it. Tiers 0 and 1 keep every document on your own instance.

The score is the product

A converter that silently returns garbage is worse than one that fails. Every response carries a score and the signals behind it, so you can route on quality instead of hoping.

$ curl -X POST $URL/v1/parse \
    -H "X-Api-Key: $KEY" -F "file=@invoice.pdf"

{
  "status": "complete",
  "parser": "pdf-inspector",  "tier": "local",
  "score": 0.843,
  "signals": { "density": 429.0, "replacement": 0,
                 "alnum_ratio": 0.928, "vowel_ratio": 0.357 },
  "escalations": [],
  "engine_version": "2026-09-03",
  "duration_ms": 21
}

An exhausted ladder is not an error. If every permitted tier fails, you still get the best Markdown obtained, its score, every signal, and a warning saying why it stopped. A low score is a routing signal — send it to a human — not a failure.

engine_version changes whenever a scoring weight or guard threshold does, so you can tell whether two results are comparable. A fingerprint test makes forgetting impossible.

Shape of the API

POST /v1/parse

Returns 200 with the result if it finishes inside the deadline, or 202 with a job id if it does not. Both are success — the work continues either way.

GET /v1/jobs/{id}

Poll a spilled job. Failures carry a real status — 415 unsupported, 503 at capacity — not one flattened code.

GET /v1/rules

The routing policy actually in effect, including which settings need a restart before they apply.

GET /readyz

Readiness. Sheds only when saturated and there is another instance to route to.

What it is built on

Every parsing engine is excellent at one thing and poor at others. A text extractor is instant and useless on a scan. An OCR engine reads anything and mangles a table it never needed to touch. A vision model reads the unreadable and will confidently invent a figure.

A hosted parsing service makes you pick one. It runs its pipeline over every document you send — the same treatment and the same per-page charge whether the text was sitting in the file all along or the page is a fax of a photocopy. You inherit that engine's weaknesses on every document, including the ones that never needed it.

This runs all of them and chooses per document. Text extraction answers in milliseconds when the text is already there. OCR runs only on the pages that have none. Each result is measured before it is accepted, and the response names which engine produced it — so you get each one's strengths without paying its weaknesses everywhere else.

Because they all run locally, choosing well costs nothing per document, adds no network round trip, and means your files never leave your instance — which matters rather a lot when they are invoices and contracts.

pdf-inspector

Tier 0 for PDFs. Reads the embedded text layer and reports which pages have none.

anydoc

Tier 0 for office formats. docx, xlsx, pptx and csv are ZIP archives of XML — no recognition needed.

liteparse

Tier 1. Rasterises with pdfium and runs tesseract over the pages that have no text of their own.

All three are pinned to exact versions. An upstream crate once resolved nine minor versions ahead of a caret range and had changed its API in four ways — the pins exist so that surfaces as a build failure rather than as quietly wrong output.