Aadhaar OCR demos work. The demo uses a flat, well-lit scan of a freshly printed card. Your document population is field officers photographing laminated cards in a customer's doorway, and the gap between those two is where extraction projects fail — quietly, because the failures return HTTP 200.

This is a list of what actually breaks, drawn from the cases that separate an extraction pipeline that works from one that returns plausible-looking wrong data. Every one of them is testable before you integrate.

The failure mode that returns 200

Start here, because everything else is a special case of it.

OCR is probabilistic. On real documents a few per cent of reads will fail, and the question that decides whether your pipeline is sound is what the API does with that. There are three possible behaviours and only one of them is safe.

It can return null for the field it could not read. It can return its best guess. Or it can fail the whole request. The middle option is the dangerous one, and it is common, because a guess makes the demo look better and the accuracy figure higher.

A guessed date of birth is indistinguishable from a read one once it is in your customer record. Nothing downstream knows the difference, and the failure surfaces months later as a mismatch against a bank record that nobody traces back to the extraction step.

So the first question to ask a vendor is not "how accurate is it" but "what do you return when you cannot read a field", and the second is "how do I tell the difference from the response". A per-field null plus a warnings array answers both. A confidence score alone does not, because you still have to pick a threshold and you have no data to pick it with.

Dates that do not exist

Older Aadhaar cards carry a year of birth rather than a full date. Not a rare edge case — a substantial share of cards issued before the format settled, which is to say a substantial share of the adult population.

An extraction API has to decide what to put in the date field for those cards, and the tempting answer is the first of January. It fills the schema, it parses, and every downstream system accepts it.

It is also a fabricated date that looks exactly like a real one. A KYC process that compares date of birth across documents will now compare a real date on the PAN against an invented one from the Aadhaar and reject a legitimate customer, or worse, accept a match that means nothing.

The correct behaviour is a null date and a separate year field, with a warning saying which you got. It is more work for the integrator and it is the only version that does not manufacture data.

Names, initials and the match that fails later

The single most common cause of unexplained manual review in Indian KYC is a name mismatch between documents that both belong to the same person.

A PAN prints the name in upper case. An Aadhaar prints it in title case. One expands an initial the other abbreviates. One carries a middle name the other omits. Neither is wrong; they were captured by different processes years apart from different forms.

Some extraction APIs try to help by normalising — expanding S. Kumar into Suresh Kumar, or reordering surname and given name into a canonical form. This is a guess dressed as a feature. If the expansion is wrong, you have written a name into a KYC field that appears on no document the customer holds, and the resulting mismatch is untraceable because the field looks perfectly ordinary.

Return what is printed. Do the normalisation in your matching layer, where you can tune it, log it, and show a reviewer both original strings when the score is borderline. Three things make that layer work: compare on a case-folded, punctuation-stripped, token-sorted form; treat a single-letter token as matching any word beginning with that letter; and keep the printed originals so a human can answer the question the score could not.

Why address components are a trap

Every extraction API is asked for the address split into line, locality, district, state and pincode. It is an obvious request and the schema looks tidy.

Aadhaar addresses are not consistently structured. They are free text with conventions that vary by enrolment centre, by decade, and by whoever typed them. A splitter will be right most of the time and wrong the rest, and there is no signal in the output telling you which case you are looking at.

Components that are wrong a fifth of the time are worse than one honest string, because the caller cannot tell which fifth. A pipeline that routes on state will misroute silently. One that matches on district will fail matches that should have succeeded.

Take the full line and the pincode, which is the one component with a distinct shape and a checkable range, and do any further parsing yourself where you can measure the error rate against your own population.

The sixteen-digit number that is not an Aadhaar number

Newer cards print a Virtual ID alongside the Aadhaar number. It is sixteen digits to the Aadhaar number's twelve, and it exists so a resident can authenticate without disclosing the number itself.

A detector that matches on digit runs will find it. Depending on how it is written it will then either return the VID as an Aadhaar number, or take twelve of its sixteen digits and return those, which is worse because the result is a plausible twelve-digit number that belongs to nobody.

The defence is the checksum. Aadhaar numbers carry a Verhoeff check digit, and validating it rejects a VID, an enrolment number, a date sequence and an account number that happens to be the right length. It costs microseconds and it is the difference between a detector that is right and one that is usually right.

Ask any vendor directly whether twelve-digit candidates are checksum-validated. It is a yes or no question and the answer tells you a great deal about the rest of the implementation.

The back of the card

The address is on the back. The number is on the front. A UIDAI download puts both sides on one page, frequently with one of them rotated ninety degrees relative to the other.

An extraction pass that reads the first orientation it finds text in will return a complete-looking response with the address missing, and the response will not say why, because from the reader's point of view nothing went wrong.

Every orientation has to be scanned, and both sides merged, before the result is assembled. The tell that a vendor does this is whether the response says which sides it found. If it cannot tell you, it did not look.

A test set you can build this afternoon

None of the above needs a benchmark suite. It needs about fifteen documents drawn deliberately from your own archive, which will tell you more than any published accuracy figure.

Run those against any extraction API, including ours, before you write the integration. The results will be more useful than a percentage, because they tell you which of your documents you will be handling by hand — which is the number that actually determines whether the project works.

Extraction that declines to guess. Unreadable fields come back null with a warning naming the problem, year-only cards return a year rather than a synthesised January date, and twelve-digit candidates are Verhoeff-validated so a Virtual ID is never returned as an Aadhaar number. Nothing is retained.

Get API Access