One extraction engine, any document type
A new document type should be configuration, and two customers sharing that type still need their own complete field set, not one global list.
The demo that only works for one document
The document extraction demo goes well. Your AI reads a supplier contract, pulls out the payment terms, the liability cap, and the renewal date, and drops them into a clean form ready for the ERP. Everyone in the room is convinced. Then someone from finance asks the obvious next question, “great, can it do invoices too?”, and the honest answer is “give us a quarter.” That gap, between a system that extracts one document type and a system that can take on the next one, is where most of the value quietly leaks out.
It leaks because in most builds every new document type is its own project. New fields, new prompts, new endpoints, new tests, a fresh slice of the roadmap. The contract extractor took three months, so the invoice extractor will take about the same, and the purchase order after it, and the statement of account after that. The system looks like it works, and it does, for exactly the document types someone already paid to build.
There’s a quieter version of the same leak. The second customer is also in trade, also wants contracts, and still can’t use the first customer’s field list. Payment terms that mattered for plastics packaging don’t match what a freight forwarder needs on the same kind of file. If you shipped one global contract form, you now have a product that only fits the demo.
Where document extraction sits in the system
This is the action side of the platform, the part that turns a document into structured data another system can act on, and it lives on the far side of the line drawn in chat is not enough. Document extraction is the job of pulling specific fields out of a document in a fixed, checkable shape, an invoice number, a total, a currency, a due date, so an ERP or a billing run can consume the result the same way every time. The architecture overview placed this engine next to the chat service; this piece is about the one property of it that decides whether it ever pays off, which is how much it costs to teach it a new document type, and how much it costs to teach the same type a second field set.
Where things go wrong
The default way to build extraction is one document type at a time, and it feels responsible while you’re doing it. Contracts are the urgent need, so you build a contract extractor, with endpoints that know about contracts, code that knows contract fields, and tests written against contract samples. It ships, it works, everyone’s happy.
The bill arrives with the second type. Invoices share almost everything with contracts at the system level, in that both get retrieved, read by a model, checked, stored, and handed to another system, but because the first build wired those steps around the word contract, none of it comes for free the second time. You copy the contract code, find-and-replace, adjust the fields, write fresh tests, and three months later you have two extractors that do the same thing to different documents and start drifting apart the moment either one changes.
Do that four or five times and you’re maintaining four or five near-identical pipelines. A fix to retrieval has to be applied in five places. A new storage rule has to be remembered in five places. And the queue of document types the business actually wants, purchase orders, statements, delivery notes, credit notes, keeps growing faster than the team can hand-build them, so the real story of the system becomes the list of documents it can’t process yet.
The trap that looks like progress is shipping one global field list per type. Contracts become the contract fields. Invoices become the invoice fields. That works until the second organization shares the public type and still needs different columns, different valid values, and different wording. You either fork the type (and you’re back to a new build), or you union every customer’s fields into one bloated form that nobody can submit. The type-only registry was incomplete; it stopped one axis too early.
What actually changes between document types
The way out starts with a boring observation about what’s genuinely different between a contract extractor and an invoice extractor. Line them up and almost everything is the same. Both find the right document in the search layer, assemble its text, make one call to a model, check the result, store it with an audit trail, and expose it through an API. That whole spine doesn’t care what kind of document is flowing through it.
The public type still matters. Users tag a file as a contract or an invoice. They open /contract, not a customer-specific URL. Storage for extracted records stays per type, so that family stays stable on purpose, and what changes is the complete field set. Which fields you want out, what a valid value looks like, how you word the instructions, and optional business rules sit together as one artifact. Two customers can share the contract family and still bind different complete field sets. You copy to customize; you don’t merge two strict schemas at request time.
Once you see both axes, the design writes itself. You build the shared spine once, as a single generic engine. You describe each public type as a family. You describe each customer’s fields as a complete field set bound to that customer’s identity tenant. Adding a document type stops being an engineering project. Adding a second customer with different columns stops being a fork of the type.
How to tell a config change from a real build
This gives you a clean test for any new request. If someone wants a new public type (statement of account after invoices), and the only things that change are destination plus a first complete field set, it’s a family plus configuration, and it should take about as long as writing those artifacts down. If someone wants different columns on contracts they already tag as contracts, it’s a new complete field set of the existing family, bound to their tenant. If they want the same fields as a field set you already ship, it’s a binding, with no new family and no new form.
Concretely, teaching the engine looks like adding a small bundle that says these are the fields, this is what valid looks like, here’s how to instruct the model, here’s the form layout. The engine discovers families and field sets on its own and serves them through the same endpoints every other type already uses, with the public type still in the URL. How that discovery actually works under the hood is the technical pair’s job; the business point is that the unit of work is a complete field set, not a quarterly extractor project and not a global contract spreadsheet.
The rare exception is worth naming so this doesn’t sound like magic. Once in a while a document type needs something the shared spine genuinely can’t express, an unusual cross-field rule, a truly different processing step, and then you do write a little code for that one type. A good design makes that the exception you notice because it’s rare, instead of the default you dread because it’s every single time.
Reality check
None of this is free, and the config-driven engine is more expensive to build than a single hard-coded extractor. If you only ever need one document type for one customer, build the one and move on, because the generic machinery is overhead you won’t recover. The payback starts around the second type, and again around the second customer who can’t share the first field list.
The bigger cost isn’t code at all, it’s ownership, and no configuration format decides it for you. Someone has to say which fields count for each customer on each public type, what a valid value looks like, and where the extracted data is allowed to land. That’s the same governance the consistent tagging discipline needed, and it doesn’t disappear because the engine got easier to extend. If anything, making new field sets cheap to add raises the stakes on getting those decisions right, because you’ll be adding a lot more of them.
And a prompt is still a prompt. Handing the engine a new field set gets it running in a day, but messy, high-variance source documents still need their instructions tuned before the extractions are trustworthy. Configuration removes the engineering tax on new types and new customers; it doesn’t remove the work of getting extraction quality right for a hard document.
Conclusion
The instinct on a document-AI roadmap is to plan it as a series of extractors, one project per document type, each with its own timeline, and that instinct is exactly what bakes in a cost that grows with every type you add, until the roadmap is mostly a backlog of documents you can’t process yet.
The platforms that actually scale flip the unit of work twice. They build the extraction spine once and treat every new public type as a family handed to an engine that already knows the job. They treat every customer’s columns as a complete field set bound to that organization’s identity tenant, so two companies can share invoice in the URL and still not share a form. The contract extractor and the invoice extractor and the fifty that follow are the same machine reading different configuration. Handling a new type, or a new customer on an old type, is measured in how long it takes to describe that field set, small enough that nobody has to put it on a quarterly roadmap.
The technical pair for this piece is Building registry-based extraction for any document type.
Ideas, opinions, and tone are mine. AI helped with the language.
For more articles on AI-native document management visit the pialgorithms blog.
pialgorithms | document management software | ai engineering services