openreading

Every provider fails somewhere · route around it

The unified interface for every reading.

Every document-processing provider fails somewhere. Textract, Google Document AI, Reducto, PyMuPDF, Tesseract, all of them. openreading puts one API in front of the lot, and they all return the same result format. Switching, cascading or racing them is a config change.

See the commands

Launching soon. Leave an email and we will tell you the day it is up.

make verify green · offline gate on every commit pre-1.0 · not yet on PyPI 127 providers researched
Works with
aws-textractgoogle-document-aiazure-document-intelligencereductochunkrpulsenuextractopen-ocranthropic-claudepymupdftesseractdoclingqwen-vl
Features ↓

Features

Six things the product does.

Product

How it works, in order: the output, the grid, strategies, agents.

Product · The output

One document. Every provider. Same format back.

Only --backend changes between these runs. Same structure back every time: document → pages → blocks, coordinates and confidence on a [0,1] scale, originals kept in bbox_native. Where providers disagree, compare shows you exactly where.

openreading parse sample1.pdf --backend
{
  "schema_version": "0.3",
  "status": { "state": "succeeded" },
  "backend": { "id": "pymupdf", "type": "oss_library",
                "output_paradigm": ["block_tree"] },
  "document": {
    "page_count": 2,
    "pages": [{ "page_number": 1, "width": 612.0, "height": 792.0,
                "unit": "pdf_point",
      "blocks": [{ "type": "text", "native_type": "text",
                   "text": "FTCSTMT042 042 20140531 UDSPDFSTMT …",
                   "bbox": { "x": 0.05882, "y": 0.00387, "w": 0.41671,
                             "h": 0.01010, "page": 1,
                     "bbox_native": { "coords": [36.0, 3.064, 291.024, 11.064],
                                      "origin": "top_left", "unit": "pdf_point" } },
                   "reading_order": 0 }] }]
  },
  "warnings": [{ "code": "confidence_unavailable",
                 "field": "block_confidence",
                 "message": "PyMuPDF is a deterministic parser;
                              per-element confidence does not exist" }]
}

The local tabs are real openreading parse output; the aws-textract tab is a captured response converted the same way. And note warnings on pymupdf: it cannot produce confidence, so the field is left out and a warning says why. No value is ever made up.

openreading does not make any provider better at reading your document.

It makes the choice reversible, and gives failure a strategy instead of an outage.

Product · The 3×3

Three features. Three ways in. The whole product.

Parse gets any document into one schema. Compare tells you which provider to trust. Strategy encodes that decision so it keeps happening without you. And each feature works for three audiences: a human at a terminal, a program, or an agent.

CLI a human at a terminal API Python · HTTP, one contract Agent an LLM acting on typed output
parse any document, one schema openreading parse on a file, a directory, a glob, or URLs run() / run_batch() · POST /v1/parse, /v1/batch driven from docs/llms.md, the self-contained agent briefing
compare who missed what openreading compare on files, or on two folder runs compare() · POST /v1/compare typed verdicts, built to be branched on
strategy the decision, encoded parse --strategy + validate / plan / explain / replay / calibrate run(strategy="x") · a strategy: id on every run endpoint every decision lands in a typed, replayable trace

Every cell speaks the same JSON Schemas. The product grows by deepening the cells it has, not by adding boxes. A new provider deepens parse. A new verdict deepens compare.

Product · Strategies

Failure handling you write once, in YAML.

A strategy is your answer to "what happens when it fails?", written down and versioned with your code. It is testable offline. If you have no openreading.yaml, nothing changes.

openreading.yaml
version: 1

strategies:
  cheap_first:             # local first; escalate only when the result looks bad
    try: [pymupdf, reducto]
    escalate_when: looks_bad

  hosted_duel:             # run both, keep the better result
    parallel:
      - reducto
      - azure-document-intelligence
    pick: best

On a digital PDF, cheap_first accepts pymupdf and reducto never runs. On a scan, the gate fires, reducto runs, and the response says so with a quality_escalated warning. explain shows why a run went the way it did; replay re-runs it offline; calibrate tunes thresholds from your own documents.

Deterministic by default. LLM decision points stay off unless an operator turns them on, and even then they only pick between options you wrote.

Product · Agents

The third user is not a person.

Most document tools assume a human reads the output. openreading assumes the caller might be an LLM, so the contract is built for that.

One briefing file. Point your assistant at docs/llms.md and it can drive the whole product. Providers, flags, exact JSON shapes and recipes are all in there.

Outputs you can branch on. Status is a typed state; a partial result says "partial", never a bare success; warnings carry codes; a compare verdict is EQUIVALENT or DIVERGENT with a score. And every strategy run leaves a typed, replayable trace, so an agent can show its work afterwards.

The callable surface is coming soon. That means openreading mcp and triage. What you get today is the briefing, the typed contracts and the traces.

Product · Intent Coming soon

Every API lets you say which fields you want. None lets you say which must be right.

What you extract depends on why you are reading. Two callers send in the same tax return. The first is archiving it, so every field matters. The second is computing qualifying income, so only six fields matter.

Caller two: compute qualifying income 6 fields carry the outcome
  • Schedule C line 31net profit  +
  • Schedule C line 12depletion  +
  • Schedule C line 13depreciation  +
  • Schedule C line 24bmeals  −
  • K-1 line 1ordinary income  ×
  • K-1 ownership %scales the subtotal
Everything else on the same pages 40 fields, given the same effort as the six
  • preparer addressirrelevant
  • preparer PTINirrelevant
  • filing statusirrelevant
  • memo linesirrelevant
  • mailing ZIPirrelevant
  • signature dateirrelevant

…and 34 more, each given as much attention as line 31.

The whole industry stops one step short. Ten hosted APIs, eight LLM frameworks, six IDP products, fourteen benchmarks: none lets you mark which fields are critical. A field is either in the schema or out of it. So the double-check spent on the preparer's ZIP came out of the budget for the depreciation line that got read once.

What ships today is the layer underneath. The intent layer itself is still a thesis and a spec in the repo, with the tests that could disprove it written down first. It would let you mark fields as critical and declare the computation they feed.

Ways to run it

Four, from "install it yourself" to "we run it in your cloud."

Ways to run it

Same product, four ways to get at it.

01 · RUN IT YOURSELF

Open-core. Everything installs.

One package gives you the command line, the Python library, an HTTP API and the web UI. Every provider connector is in it, along with strategies, compare and batch. It runs single tenant on your machine with your keys. You set it up and you operate it, and nothing touches our infrastructure.

Ready

02 · WE RUN IT, YOUR KEYS

Hosted, your provider accounts.

The same product, except we operate it. We call the providers with your keys, so the usage still lands on your accounts. There is nothing for you to install, host, upgrade or keep running.

Coming soon

03 · WE RUN IT, OUR KEYS

Hosted, our provider accounts.

We supply the provider accounts as well. You sign in and start sending documents. There are no keys to collect and no provider accounts to open.

Later

04 · YOUR CLOUD

Managed, inside your cloud.

We set it up and run it in your own environment, and we add the pieces that only matter at organisation scale: user accounts, administration and support. Your infrastructure, your data and your keys. You keep full control of all of it.

Enterprise · later

The reading itself is identical in all four. Same connectors, same strategies, same output. What changes is who operates it and whose provider accounts get called. The open-core version leaves out only what someone else running it for you would provide: user accounts, provisioned keys, uptime and administration.

Get started

Ten seconds to a parsed document, on your own machine, no key required.

Get started · CLI & Python

Install, parse, compare.

shell
$ pip install 'openreading[pymupdf,tesseract]'

$ openreading backends                 # who is ready, and which env vars are missing
$ openreading parse loan.pdf --backend pymupdf          # no key, no network
$ openreading parse invoices/ --backend pymupdf         # a directory → one batch JSON
$ openreading compare loan.pdf --backends pymupdf,tesseract --format diffs
python · the same contract
import openreading

doc = openreading.run("loan.pdf", backend="pymupdf")
doc.keys()
# schema_version, status, backend, document, usage,
#   warnings, backend_raw, channel_provenance
compare --format diffs · the verdict, not the diff wall
① CONTENT   ✔ EQUIVALENT  content shared by all: 1.00 · 0 real misses
② TABLES    table counts differ: {'pymupdf': 1, 'tesseract': 0}

Same JSON Schemas on every surface: the command line, Python, and an HTTP API you run yourself (openreading serve/v1/parse, /v1/batch, /v1/compare, /v1/jobs). A web UI comes with it, at openreading serve-ui. All of that is in the package. Hosting it for you is the separate thing.

Docs

Everything in one place, for people and for the LLM helping them.

Docs · The map

Nine guides, one line each.

docs/user-guide.mdStart here. It routes you to the right doc for every job.
docs/llms.mdThe agent briefing. Point your LLM at this one file.
docs/cli.mdEvery command: parse, batch, compare, strategy, replay.
docs/server.mdThe HTTP server and its endpoints.
docs/schema/The request, the response, and the provider spec.
docs/strategies/The YAML spec, a cookbook, and execution semantics.
docs/compare/How verdicts and diffs work.
docs/credentials.mdHow keys are read, held, and never stored.
docs/thesis.mdWhy openreading exists. The intent argument in full.

Status

What is ready, and what is not. Never blurred.

Status · Feature table

Ready, or coming soon.

FeatureStatus
Parse any document through any provider, in one format Ready
Batch a directory or a glob into one JSON file Ready
Compare providers, with a verdict per document Ready
Strategies: cascades, races, explain, replay, calibrate Ready
Local parsers that run entirely on your machines Ready
HTTP API + web UI you run yourself Ready
Agent briefing (docs/llms.md) Ready
Framework-loader providers Coming soon
Hosted by us, your provider keys Coming soon
Hosted by us, provider accounts included Later
Managed in your own cloud Enterprise · later
MCP server and triage, the callable agent surface Coming soon
Intent layer: mark critical fields, declare the computation Coming soon

Pre-1.0, not yet on PyPI. See ways to run it for what is available today and what is not. No customer logos because there are no customers yet.

FAQ

The questions everyone asks, answered the way they get asked.

FAQ

Straight answers to the usual questions.

What do I actually get if I just install it?

All of the reading. The command line, the Python library, an HTTP API you run (openreading serve) and a web UI (openreading serve-ui), plus every provider connector, the strategy engine, compare and batch. Single tenant, on your machine, with your keys.

It is not a client for a service of ours. It calls the providers directly and nothing routes through us. What it leaves out is what only matters when someone else runs it for you: user accounts, provisioned provider keys, uptime and administration.

Are my documents going through your servers?

Not when you run it yourself. The package talks to the providers directly, and the local parsers do not leave your machine at all.

On the hosted editions, yes. That is what hosting means, and it is the trade you make for having nothing to operate. It is also why the self-hosted version exists and stays open.

Why not just call Reducto or Textract directly?

Do that, if you will only ever use one provider. The work shows up with provider two: converting a second output format, handling a second set of errors, a second async model, a second set of retries.

You still call the vendor directly through openreading, with your key and your account. The key is held in memory for the call and never stored.

This is a wrapper.

Yes, and the wrapping is the hard part. Coordinates on one [0,1] scale with the originals kept in bbox_native. Confidence on one scale too (Textract and Tesseract report 0–100). Sync, polling and webhooks unified under one Job state machine. A warnings[] entry wherever a provider cannot answer, instead of a made-up value.

The same format has been implemented independently for every provider. That is what proves it holds.

What happens when a provider changes their API?

The break lands in one connector, not in your code. Connectors are tested against recorded responses plus injected failures, and real API behaviour is checked in a separate keyed lane.

Keeping connectors current is the service's job.

Our data cannot leave our environment.

Use the local parsers. pymupdf and tesseract need no keys and no network; docling and qwen-vl run against your own container or endpoint. Your documents stay on your machines.

To be clear: openreading certifies nothing on your behalf. It gives you parsers that run entirely inside your environment. Any certification is on you.

We already have a parser.

Keep it. Start by measuring it: run compare with your parser and one other provider over your own documents, and read what each one missed. You get one verdict per document instead of a wall of findings.

If your parser wins, you now have evidence that it does.

We are launching soon.

Leave an email and we will tell you the day it is up. Bring the document that broke your last provider.

See what is ready

One email when it launches. Nothing else, and no list to unsubscribe from.

Get launch updates

One email the day openreading is up. Nothing else.

Form not loading? Open it in a new tab.