OpenContracts: a self-hosted citation graph for document corpora
The open document intelligence platform for builders and hackers - DMS for the agentic world
At a glance
- What is it?
- OpenContracts turns a folder of documents into a queryable citation graph with human annotation, LLM extraction and an MCP server on top. It is MIT-licensed and Python-based, and the interesting part is the graph model, not the chat interface.
- Who is it for?
- Adopt OpenContracts if you hold a corpus whose value is in the cross-references between documents (filings, statutes, contracts, policy sets) and you have Python capacity to run Celery workers and a Postgres-backed stack. Do not adopt it if you need a general-purpose DMS with mature records-management workflows, or if your documents are standalone with no citation structure to mine.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem OpenContracts picks: citations between documents, not text inside one
Most document AI tools answer questions about a single file. OpenContracts is built around a different unit of work: the edge between two documents. The README frames the output as a programmable citation graph, and the demo described there takes 36 SEC filings and wires them to the Delaware General Corporation Law, the Securities Act and the SEC rules they cite, down to the section. That is a specific claim about resolution granularity. Detecting the string 8 Del. C. 141(a) in a filing is easy; deciding which section of which statute it points at, and storing that as a first-class edge, is the harder engineering problem, and it is the one this project has chosen.
The audience follows from that. The description says builders and hackers, and the README repeats the phrase platform, not a black box. This is not aimed at a legal operations team that wants a hosted review tool with a support contract. It is aimed at an engineering team that has a corpus, has permission to self-host, and wants to write code against the graph. The MIT licence removes the usual procurement conversation, and the Python stack means the extension points are importable rather than pluggable through a vendor SDK.
Corpus, document, annotation, relationship: the four objects the API exposes
The README names the queryable set directly: corpuses, documents, annotations, relationships, extracts. That is the data model in one line, and it is worth reading carefully because it tells you what the system does and does not store. A corpus is a container. A document belongs to one. An annotation is a span or region a human or an agent has marked. A relationship is the edge, and the citation graph is just the relationship table filtered to citation edges. Extracts are the structured output of a fieldset run.
Three surfaces sit on that model. A GraphQL endpoint and a REST endpoint serve applications. A Model Context Protocol server serves agents. A React UI serves people. The README's claim is that all three read the same graph, so anything visible in the UI is reachable over the API. That is a design commitment worth testing early, because the failure mode is a UI feature that quietly bypasses the API layer.
The agents are document-scoped or corpus-scoped. The README example constructs an agent with agents.for_document(123, corpus=45), which implies the agent carries both a document context and a corpus context, and the answers are described as grounded in the annotations and citations the team has built. Grounding against annotations rather than raw text is the architectural choice that distinguishes this from a generic retrieval pipeline. It also means an empty annotation layer produces an ungrounded agent.
One click installs the intelligence bundle, and that hides a queue
The README describes a Set up button that installs the intelligence bundle: agents describe and summarize every document, and the reference web is built. The phrasing is deliberately frictionless, and the underlying mechanism is not. Structured extraction is stated to fan out over Celery workers, and parsing, embedding and thumbnailing are described as swappable pipeline components. So the one click is enqueueing a set of Celery tasks, and the corpus is not fully wired until those tasks drain.
That matters for sizing. A corpus of a few hundred documents will finish while you watch the progress indicator. A corpus of tens of thousands will not, and the worker pool becomes the thing you tune. The README does not give throughput numbers, and I have not run it, so treat any estimate you see elsewhere as unverified. What the material does support is the shape of the load: parsing is per-document, embedding is per-chunk, thumbnail generation is per-page, and citation resolution is per-detected-reference. Those are four different task profiles with different costs, and they share one queue unless you configure otherwise.
The backlog behaviour is the more interesting detail. Law the library does not hold yet is not discarded; it is tracked as a backlog and drawn as dashed nodes until you ingest it. This is a genuinely useful design decision. A citation graph that silently drops unresolved references looks complete and is not. One that renders them as empty nodes tells you exactly which statutes to buy or fetch next.
Fieldsets, the extraction primitive, and why approve and reject matters
Structured extraction in OpenContracts is organised around a fieldset: a set of columns where each column is a natural-language query. You define the columns once, run the fieldset across a corpus, and the results land in a spreadsheet-style grid. Every cell carries a human approve or reject action.
This is a different bet from the usual schema-first extraction tool, where you write a JSON schema and the model fills it. A natural-language column is easier to author and easier to iterate on, because changing the question does not mean changing a schema and re-validating downstream consumers. The cost is that the output type of a column is whatever the model decides to return, and the README does not describe a type coercion layer. If you need a column that is reliably a date or a currency amount, that is the first thing to test.
The per-cell review is the part that makes the design defensible for regulated work. Extraction grids that produce thousands of unreviewed cells are a liability, because nobody can tell which cells a human has looked at. Attaching approve and reject to each cell turns the grid into a work queue with an audit trail. The trade-off is reviewer time, and the README offers no estimate of how long a cell takes to review. For a fieldset with twenty columns over a thousand documents, that is twenty thousand decisions, and the economics only work if the columns are genuinely decision-relevant.
The MCP server is the part most teams will actually integrate
The MCP surface is documented concretely enough to evaluate. Two endpoints: /mcp/ for anonymous access to public corpuses, and /mcp/me/ for authenticated access. Discovery is published at /llms.txt and /.well-known/mcp.json. The tool list is search_corpus, list_documents, get_document_text, list_annotations, list_relationships, list_threads, create_thread_message.
Read that list as a capability boundary. An external agent can search, enumerate documents, pull text, read annotations, walk relationship edges, and post messages into threads. It cannot, through these tools, create annotations. The README says agents can propose annotations of their own when authorized, which suggests annotation writing exists somewhere in the API, but it is not in the published MCP tool list. If your workflow depends on an external agent writing back into the graph, confirm which surface exposes that before you build on it.
The two-endpoint split is a clean answer to a real problem. Anonymous MCP access to a public corpus is exactly what you want for a documentation site or a public regulatory library. Authenticated access to private corpuses is a separate endpoint with separate auth, so you are not relying on a single token check inside the tool handler. The README does not describe the authentication scheme behind /mcp/me/, which is the first thing to read in the MCP documentation before exposing anything non-public.
Where this is the wrong tool, and what to use instead
OpenContracts assumes your documents cite each other, and that the citations are resolvable to identifiable targets. Feed it a folder of standalone invoices and the relationship layer stays empty. You get parsing, embedding, search and extraction, which is a competent document pipeline, but the citation graph, the references panel and the dashed backlog nodes all do nothing. The setup effort is not repaid.
The more common mismatch is with general-purpose document management. If you need retention schedules, legal holds, version lineage with approval workflows, or a records classification scheme, OpenContracts is not that, and the README does not claim it is. The description calls it a DMS for the agentic world, but the material shows a document intelligence platform with a corpus abstraction, not a records management system.
Paperless-ngx is the honest alternative for the adjacent job. It is a self-hosted document archive built around consumption, tagging, correspondents, document types and full-text search, with OCR as the ingestion step. The difference in approach is the unit of value. Paperless-ngx optimises for finding the document you filed; OpenContracts optimises for the edges between documents you already found. If your problem is we cannot locate our contracts, Paperless-ngx is the shorter path. If your problem is we cannot see which clause in this contract conflicts with which clause in that regulation, Paperless-ngx has no answer and OpenContracts is aimed directly at it. Running both is defensible: Paperless-ngx as the archive of record, OpenContracts as the analytic layer over a curated subset.
Licence, upgrade cost and what the release history tells you
The licence is MIT, stated in the repository metadata and repeated in the README badge. That is permissive: you can self-host, modify and redistribute, including commercially. It says nothing about the licences of the models, parsers or embedding services you point the pipeline at, and those are your responsibility. Nothing here is legal advice; if you are embedding this in a product, the third-party components in your pipeline deserve their own review.
The release history shows v3.0.0 in August 2026 with a subtitle naming corpus intelligence, authority linking and deep research, followed by v3.1.0 in September 2026, and a v3.0.0.b4 beta in February 2026. A major version landing and then a minor release a month later is normal for an active project, and it also means the 3.x line is young. If you adopt at 3.1.0, expect the API surface to keep moving, particularly around the agent and MCP layers where the feature names suggest recent work. Pin your version and read the release notes before each upgrade rather than tracking main.
Upgrade cost is dominated by the pipeline, not the application code. Because parsers, embedders and thumbnailers are swappable components, a change to the default embedder means re-embedding your corpus to keep search consistent, and a change to the default parser means re-parsing. Those are Celery-scale jobs, not a container restart. Budget for the reprocessing window whenever you change a pipeline component, and treat the choice of embedder as a long-lived decision rather than a tunable.
What to verify before you commit a corpus
Run the stock product against a representative sample first, not your whole archive. The README is explicit that the demo clips show the stock product against a local install with no custom code, which makes the documented behaviour a fair baseline to test against. Create one corpus, load twenty or thirty documents you already understand well, and use the Set up flow. Then check the citation graph by hand. Pick three references you know the correct target for and confirm the resolved edges point where you expect. That single check tells you more about fitness than any feature list.
Next, define one fieldset with columns you can grade, run it, and review the grid. The question is not whether the model returns plausible text. It is whether a reviewer can approve or reject a cell in a few seconds without opening the source document. If they cannot, the extraction layer will not scale to your corpus regardless of how good the answers look in a demo.
Finally, read the MCP documentation and confirm the auth model behind /mcp/me/ before you expose a private corpus to an external agent. The tool list published in the README does not include annotation writing, so if that is in your plan, verify where it lives. The project is MIT-licensed and self-hosted, which means the boundary of what it does is the boundary of what you can read in the source, and that is the right place to settle these questions.
Editorial conclusion
Adopt OpenContracts if you hold a corpus whose value is in the cross-references between documents (filings, statutes, contracts, policy sets) and you have Python capacity to run Celery workers and a Postgres-backed stack. Do not adopt it if you need a general-purpose DMS with mature records-management workflows, or if your documents are standalone with no citation structure to mine. Before committing, verify three things against your own corpus: how your file formats survive the default parsers, whether the fieldset extraction grid produces cells your reviewers can actually approve, and whether the MCP endpoints /mcp/ and /mcp/me/ fit your authentication model. The citation graph is the product; if your documents do not cite each other, most of it is idle.
Community notes