How to use this page
This page is different from the rest of this section. The modules explain how things work. The build sheets list what to buy. This page walks you through building one product, start to finish.
It is written for someone who has not built this before. Every step says what goes in, what comes out, and how to plug it into the next step.
| If you want... | Read... |
|---|---|
| To understand why document reading is hard | the Identity module |
| A full list of every tool, with prices | the Identity Build Sheet |
| To build the thing | this page |
Nothing here is secret. All of it is doable by two engineers in a few weeks. The hard part is not any single step. It is knowing which eight steps there are, and what to connect to what.
What Document AI actually is
Document AI takes a picture of a document and gives you back fields you can put in a database.
Picture of a PAN card goes in. Out comes: name, PAN number, date of birth, father's name. Each one with a score saying how sure the system is.
People also call it ICR, which stands for Intelligent Character Recognition. Older OCR read printed text. ICR is meant to also read handwriting and understand layout. In practice the words get used loosely and nobody minds.
What it is not:
- It is not a decision. It reads a document. It does not tell you whether to open the account.
- It is not verification. Reading "Rajesh Kumar" off a card does not prove Rajesh Kumar exists or that this is his card. That is a different product.
- It is not one model. It is six or seven things in a row. This is the single biggest surprise for teams who thought they were buying an API.
Who buys it: banks, lenders, insurers, and anyone who currently has people typing things off scanned paper. The business case is almost always we have forty people doing data entry.
The whole journey, in one table
Here is the whole thing. Eight steps. Read this table once and the rest of the page is just detail.
| # | Step | In plain words |
|---|---|---|
| 1 | Get the file | The document arrives from somewhere. |
| 2 | Check it is usable | Is it too blurry to read? Find out before you pay to read it. |
| 3 | Work out what it is | PAN card? Bank statement? You cannot extract fields until you know. |
| 4 | Read the text | Turn pixels into words. This is the bit everyone calls "the OCR". |
| 5 | Turn words into fields | A pile of words is not data. Which word is the name? |
| 6 | Check the fields are real | Does the PAN number pass its checksum? Does the date make sense? |
| 7 | Accept, review or reject | Send the uncertain ones to a human. |
| 8 | Keep the evidence | What you read, from which image, how sure you were, when. |
Steps 2, 6, 7 and 8 are the ones people skip. They are also the ones that decide whether this works in production or only in the demo. A team that builds steps 1, 3, 4 and 5 has built a demo. That is not an insult — it is just a much smaller thing than it looks.
Steps 3 and 4 are where the money goes. Steps 2, 6, 7 and 8 are where the product lives.
Steps 1 and 2 — getting a usable file
Step 1 — Get the file
What it does: the document gets from the customer to your server.
| Option | What it is | Effort | Pick this when |
|---|---|---|---|
| A. Plain upload | A file input on a web page. | Hours | Always start here. It works and it costs nothing. |
| B. Mobile SDK capture | A vendor's camera component that guides the user and rejects bad shots on the phone. | Days, plus a licence | Consumer app, high volume, and bad photos are killing you. |
| C. DigiLocker fetch | The customer consents and you receive the issued document directly. | Weeks, needs approval | You want a document that is already verified. Skips steps 2–6 entirely for supported documents. |
What comes out: a file on disk or in object storage, plus an id.
Connect it to step 2: do not pass the file around. Save it once, give it an id, and pass the id. Every later step reads from storage using that id. If you pass image bytes between services you will run out of memory on the day someone uploads a 40 MB scan.
Option C deserves more thought than it usually gets. If DigiLocker can give you the document directly, you are not reading a photograph of a PAN card — you are receiving the issued record. No blur, no glare, no OCR, no confidence score. Teams build the whole eight-step pipeline and only afterwards discover that a large share of their documents could have come down a route that skips six of the steps. Check what is available before you build.
Step 2 — Check it is usable
What it does: looks at the image and decides whether it is worth reading. Blurry, dark, cropped, upside down, or a photo of a screen.
| Option | What it is | Roughly costs | Pick this when |
|---|---|---|---|
| A. Simple maths | OpenCV. Measure blur, brightness and resolution with about thirty lines of code. | Free | Start here. It catches most of the bad ones. |
| B. A small model | A trained classifier that scores image quality. | Your own GPU time | Simple maths is passing things that later fail. |
| C. Skip it | Send everything to the OCR and see what happens. | You pay per page, so this is the expensive option | Never, once you are past a few hundred documents a day. |
import cv2
# This is the whole thing. It is not clever. It works.
def usable(path):
img = cv2.imread(path)
if img is None:
return {"ok": False, "why": "not_an_image"}
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = grey.shape
# Blur. Laplacian variance: sharp images have high variance, blurry ones low.
blur = cv2.Laplacian(grey, cv2.CV_64F).var()
# Brightness. A photo taken in the dark, or washed out by flash.
bright = grey.mean()
checks = {
"too_small": w < 800 or h < 500,
"too_blurry": blur < 100, # tune this on YOUR documents
"too_dark": bright < 50,
"too_bright": bright > 205,
}
failed = [k for k, v in checks.items() if v]
return {"ok": not failed, "why": failed,
"blur": round(blur, 1), "brightness": round(bright, 1)}
# WHAT TO CHECK
# [ ] tune the thresholds on 200 of YOUR real documents, not on the numbers
# above. A PAN card photographed indoors and a scanned bank statement have
# completely different normal ranges
# [ ] tell the user WHICH check failed. "Please try again" makes people retry
# the same bad photo. "Too dark - move to a window" gets you a good one
# [ ] let the user override after two failures and send it to human review.
# A gate with no escape hatch becomes a customer who cannot open an account
# [ ] log the scores even when the image passes. When accuracy drops next month
# you will want to know whether the images got worse
What comes out: a yes/no, and if no, which check failed.
Connect it to step 3: only files that pass go forward. Files that fail go back to the user with the specific reason. This is the cheapest step on the page and it removes more OCR cost than anything else you will do.
Steps 3 and 4 — what is it, and reading it
Step 3 — Work out what the document is
What it does: decides whether this is a PAN card, an Aadhaar, a passport, a salary slip or a bank statement.
You need this because step 4 and step 5 are different for each type. There is no general "read any document" that gives you clean fields.
| Option | What it is | Roughly costs | Pick this when |
|---|---|---|---|
| A. Ask the user | A dropdown: "what are you uploading?" | Free | Start here. Genuinely. Most products never need more. |
| B. Keyword rules | Run cheap OCR, look for giveaway words ("INCOME TAX DEPARTMENT", "Permanent Account Number"). | ≈ $1.50 per 1,000 pages | You have a few document types and they have distinctive text. |
| C. A classifier model | Azure or Google classify the document before reading it. | ≈ $3 per 1,000 | Many types, or documents arrive in bulk with no user to ask. |
Option A sounds lazy and is usually right. If the user is standing in your app uploading their PAN card, they know it is a PAN card. Asking costs nothing and is more accurate than any classifier. Save the classifier for post boxes and bulk scans, where there is no user to ask.
What comes out: a document type, and a confidence score if you used B or C.
Connect it to step 4: the document type chooses which reader you call. PAN and Aadhaar go to an identity-document API. A bank statement goes to a table extractor. Sending a bank statement to an ID reader gets you nothing useful and still bills you.
Step 4 — Read the text
What it does: turns the picture into words, with a position on the page and a confidence for each one.
| Option | What it is | Roughly costs per 1,000 pages | Pick this when |
|---|---|---|---|
| A. Plain OCR | AWS, Google or Azure. Just gives you words and boxes. | ≈ $1.50, all three within cents of each other | You will do the field-finding yourself in step 5. |
| B. Identity-document API | AWS Textract AnalyzeID and equivalents. Knows what a passport and a driving licence look like. | ≈ $10–25 | ID documents specifically. It does steps 4 and 5 together. |
| C. Prebuilt or custom models | Azure and Google models for invoices, receipts, and your own trained ones. | Prebuilt ≈ $10, custom ≈ $30 | A document type with a stable layout and real volume. |
| D. Self-hosted | PaddleOCR, docTR, Tesseract on your own machine. | No per-page fee. You pay for CPU, and GPU if you use a vision model | Data cannot leave your building, or volume is high enough that per-page pricing hurts. |
Plain OCR is a commodity. Do not spend a week choosing. AWS, Google and Azure price it at roughly $1.50 per 1,000 pages and match each other to the cent. Pick the one your infrastructure already uses. The interesting choice is B versus A-plus-your-own-step-5, and that is a build-versus-buy decision, not a vendor comparison.
What comes out: a list of text pieces. Each has the text, a box saying where it sat on the page, and a confidence between 0 and 1.
Connect it to step 5: this is the most important handoff on the page, so it has its own section below.
The wiring between step 4 and step 5
Most guides stop after "call the OCR API". That is where the actual work starts, so this is the part we are going to be precise about.
Step 4 gives you words. Step 5 needs to produce fields. The contract between them is where teams lose a fortnight.
# STEP 4 GIVES YOU THIS. Every OCR engine returns roughly this shape, with
# different key names. Normalise to one shape IMMEDIATELY, in one small adapter
# per engine. Then nothing downstream knows or cares which engine you used --
# and swapping engines becomes a one-file change instead of a rewrite.
ocr_output = {
"doc_id": "doc_8814",
"engine": "textract-v1", # keep this. You WILL need to know later.
"pages": [{
"page": 1,
"words": [
# text, box (x, y, width, height as 0-1 fractions), score
{"text": "INCOME", "box": [0.10, 0.05, 0.08, 0.03], "score": 0.99},
{"text": "TAX", "box": [0.19, 0.05, 0.04, 0.03], "score": 0.99},
{"text": "ABCDE1234F","box": [0.10, 0.44, 0.22, 0.04], "score": 0.97},
{"text": "RAJESH", "box": [0.10, 0.52, 0.12, 0.03], "score": 0.94},
{"text": "KUMAR", "box": [0.23, 0.52, 0.11, 0.03], "score": 0.95},
],
}],
}
# STEP 5 MUST GIVE YOU THIS. Note what is carried through: the score, and WHERE
# on the page it came from. Without those two, steps 6, 7 and 8 cannot work.
fields_output = {
"doc_id": "doc_8814",
"doc_type": "pan_card",
"fields": {
"pan": {"value": "ABCDE1234F", "score": 0.97,
"source": {"page": 1, "box": [0.10, 0.44, 0.22, 0.04]}},
"name": {"value": "RAJESH KUMAR", "score": 0.94, # LOWEST of its words
"source": {"page": 1, "box": [0.10, 0.52, 0.24, 0.03]}},
},
"missing": ["father_name", "dob"], # ALWAYS list what you did not find
}
# WHAT TO CHECK
# [ ] boxes as fractions of the page (0-1), never pixels. Pixels break the
# moment someone uploads the same document at a different resolution
# [ ] a joined field takes the LOWEST score of its parts, not the average.
# "RAJESH" at 0.99 and "KUMAR" at 0.60 is a 0.60 name, not a 0.80 one.
# Averaging confidence is how bad reads get through
# [ ] "missing" is a real list, not an absence of keys. "We could not find the
# date of birth" and "we never looked for it" are different facts
# [ ] keep the box. When a human reviews this in step 7, you want to highlight
# the exact spot on the image. Reviewers go three to five times faster when
# they can see where the value came from
# [ ] keep the engine name and its version. When accuracy shifts next quarter,
# the first question is whether the vendor changed the model
# [ ] normalise every engine into this ONE shape in an adapter. Do not let
# Textract's key names leak into your database
The rule underneath all of that: carry the confidence and the position all the way through. They feel like debugging information at step 5. By step 7 they are the product, and by step 8 they are your evidence.
Steps 5 and 6 — fields, and checking them
Step 5 — Turn words into fields
What it does: decides which of those words is the name, which is the PAN number, which is the date of birth.
| Option | What it is | Roughly costs | Pick this when |
|---|---|---|---|
| A. Position rules | On a PAN card the number is always in the same place. Take whatever text sits in that box. | Free | Fixed-layout documents. Fast, free, and completely predictable. |
| B. Label matching | Find the word "Name", take what is to the right of it. | Free | Forms with printed labels. Handles small layout shifts. |
| C. Pattern matching | A PAN is five letters, four digits, one letter. Find anything that matches. | Free | Fields with a strict format. Combine with A or B. |
| D. Let the API do it | You picked option B in step 4, so fields come back already extracted. | Included in the $10–25 | ID documents. Least code by a distance. |
| E. Ask a language model | Give the model the OCR text and ask for JSON. | Per token, so it depends | Messy or varied layouts where A, B and C all fail. |
Option E has a trap. A language model will happily invent a plausible date of birth if the OCR did not read one. You asked for JSON with a dob field and it will give you a dob field. Two rules if you use it: pass only the OCR text and never the image description, and then check every returned value actually appears in the OCR output. If the model returns something the OCR never read, throw it away and mark the field missing. This is the same rule as the refusal gate in Build Sheet 06 — the value must be present in the retrieved text, not merely plausible.
Connect it to step 6: pass the whole field object, with scores and boxes. Do not flatten it to plain strings here. Step 6 needs the scores and step 7 needs the boxes.
Step 6 — Check the fields are real
What it does: catches wrong values that the OCR was confident about. This is the step that separates a demo from a product.
A confidence score tells you how clearly the system saw the characters. It does not tell you whether the answer is right. An OCR engine can read a smudged 8 as a 3 with 0.98 confidence.
| Check | What it catches | Costs |
|---|---|---|
| Format | A PAN that is not five letters, four digits, one letter. An eleven-digit phone number. | Free |
| Checksum | Aadhaar has a check digit. So do IFSC and GSTIN and many others. A single misread character fails it. | Free |
| Cross-field | Date of birth after today. Issue date after expiry date. Age of four on a PAN card. | Free |
| Against the source | Ask the issuing authority whether this number exists and matches this name. | Per lookup, varies by provider |
The first three are free and catch most of it. Do all three before you spend money on the fourth. A field that fails a checksum does not need a paid lookup to tell you it is wrong — and sending it anyway is a cost you can remove on your first afternoon.
Connect it to step 7: each field now carries both a confidence and a validation result. Step 7 needs both, and they disagree more often than you would expect. A field can be read clearly and still be wrong; a field can be read poorly and still be correct.
Steps 7 and 8 — deciding, and keeping proof
Step 7 — Accept, review or reject
What it does: turns scores and checks into an actual decision about this document.
# Three outcomes. Never two. A system with only accept and reject will either
# reject good customers or accept bad data, and usually both.
def decide(fields, doc_type, rules):
hard_fail, weak = [], []
for name, f in fields.items():
if name in rules["required"][doc_type]:
if f["value"] is None:
hard_fail.append((name, "missing"))
continue
if not f["valid"]: # from step 6
hard_fail.append((name, f["invalid_reason"]))
continue
if f["score"] < rules["min_score"].get(name, 0.90):
weak.append((name, f["score"]))
# A failed CHECKSUM is different from a LOW SCORE. The first means the value
# is wrong. The second means we are unsure. Do not merge them into one
# "confidence" number -- you lose the ability to tell the user which it was.
if hard_fail:
return {"outcome": "review", "reason": "failed_validation",
"detail": hard_fail, "show_boxes": True}
if weak:
return {"outcome": "review", "reason": "low_confidence",
"detail": weak, "show_boxes": True}
return {"outcome": "accept"}
# WHAT TO CHECK
# [ ] "reject" is almost never the right automatic outcome for a document that
# a real customer uploaded. Route to REVIEW. Let a person reject
# [ ] the review screen shows the image with the box drawn on it, next to the
# extracted value, in one editable field. Reviewers go 3-5x faster
# [ ] thresholds live in config, are versioned, and are approved. Changing a
# threshold changes who gets accepted -- treat it as a control change
# [ ] measure the review queue every week. If it is growing faster than volume,
# something upstream broke and nobody noticed
# [ ] measure the OVERRIDE RATE. If humans accept 95% of what you send them,
# your thresholds are too tight and you are paying people to click yes
# [ ] every correction a reviewer makes is training data and a bug report.
# Store the before and after. This is the highest-value data you will
# generate and most teams throw it away
Connect it to step 8: the decision, the reason and the reviewer's edits all go into the record. Especially the edits.
Step 8 — Keep the evidence
What it does: stores what happened, so that in two years you can answer "why did you accept this document?"
This is the step that gets left until last and then never gets built. It is also the one that regulators and auditors ask about first.
| Store | Why |
|---|---|
| The original image | Everything else is a claim about it. |
| The full OCR output | So you can re-run step 5 later without paying for step 4 again. |
| The extracted fields, with scores and boxes | What you believed, and how sure you were. |
| Which engine and which version | When accuracy shifts, this is the first thing you check. |
| Which thresholds were in force | The rules change. The record must say which rules applied that day. |
| The decision, and who made it | Automatic, or a named reviewer. |
| Every correction a human made | Your best training data and your best bug report. |
Store the record append-only. A correction is a new row, never an edit to the old one. The question is not only “what do we believe now” but “what did we believe on the day we opened the account”, and an updated row cannot answer the second one.
What it costs
Document AI — what it costs per 1,000 pages
Verified May 2026Features stack, and one document can hit several meters. A non-standard document typically runs a classifier (≈$3) and then custom extraction (≈$30), so the real cost is the sum of every model that fires — not a single headline rate. This is the most common mistake in a document-AI cost estimate.
Two more things that quietly distort estimates. Google bills roughly $0.05 an hour per deployed custom processor version — about $438 a year whether or not you send it any traffic. And multi-page documents bill page by page, so a 40-page file is 40 billable pages even if you only need page one.
These figures were verified in May 2026 for Build Sheet 01 and are carried here unchanged. Cloud pricing moves. Check the vendor's calculator before you commit to a number in a business case.
Three versions you could build
The weekend version
Build: upload form → OpenCV quality check → ask the user what the document is → Textract AnalyzeID → format and checksum validation → anything uncertain goes to a spreadsheet a person looks at → store everything in one Postgres table.
You get: a working product. Genuinely. Two engineers, one weekend, and it handles real documents.
It breaks when: volume passes a few hundred a day, or the review spreadsheet gets more than one person using it.
The proper version — start here if you are serious
Build: everything above, plus — a real review interface that draws the box on the image → one adapter per OCR engine so you can switch → thresholds in versioned config → an append-only evidence table → corrections captured as training data → weekly numbers on queue size and override rate.
You get: something you can run a business on and show an auditor.
Cost shape: the API bill is small. The reviewers are the cost.
It breaks when: nothing, for a long time. This is the right build for almost everyone.
The enterprise version
Build: everything above, plus — self-hosted OCR for data that cannot leave your building → custom models per document type → a routing layer that picks the cheapest engine that will work → full audit and model governance.
Use when: millions of documents, or a data-residency rule that rules out the cloud APIs.
It breaks when: you build it too early. Most teams that start here spend six months and end up with the proper version anyway, having paid for the detour.
If you take one thing from this page: build the weekend version first, this week. Run 200 real documents through it. What you learn in those 200 documents will change your design more than any amount of planning, and you will have thrown away almost nothing.
What goes wrong
Ranked by how often we see them, not by how serious they are.
| What goes wrong | Why | Fix |
|---|---|---|
| High confidence, wrong value | Confidence measures how clearly the characters were seen, not whether the answer is right. | Step 6. Checksums and format rules, always, even on 0.99 fields. |
| Review queue quietly grows | Something upstream changed — a new phone camera, a redesigned document, a vendor model update. | Chart queue size weekly. A rising line is always upstream. |
| The cost is triple the estimate | Features stacked. Classifier plus custom extraction plus tables, all on one document. | Add up every meter that fires, per document type. |
| Averaged confidence hides a bad read | A name joined from two words took the mean instead of the minimum. | Lowest score wins on any joined field. |
| The model invented a field | A language model was asked for JSON and filled in a blank. | Check every value appears in the OCR text. If not, mark it missing. |
| Cannot explain a decision from last year | The record was updated in place, or the thresholds were never stored. | Append-only, with the threshold version on every row. |
| Works in the demo, fails in the app | Demo used clean scans. Real users send photos at night, at an angle, with a thumb over the corner. | Step 2, and test on photos your own team took on their own phones. |
Where to go next
This page covered one product. The same eight-step shape appears in others, and once you have built this you will recognise it.
Identity & Onboarding
Every tool in this space with full pricing, plus the face-match and liveness layer that sits next to this one.
Why this is hard
The background: why document reading fails, and what verification actually proves.
Grounding & refusal
The rule that stops a language model inventing a field, written out properly.
India regulation
What you are allowed to store, for how long, and what the KYC rules require.
This page is a guide, not a specification. Document AI inside a regulated process carries KYC, record-keeping and data-protection obligations. Nothing here is legal advice. Have your retention, consent and review process checked by someone qualified before real customer documents go through it.
Sources
Every figure, rule and date on this page, and where to check it. Entries are typed so you can see which numbers are primary-sourced and which are industry reporting — they are not equivalent, and treating them as if they were is how a confident wrong number gets repeated.
- vendorAWS Textract pricing — plain OCR, AnalyzeID, tables, forms and queries, and the high-volume tiering. aws.amazon.com
- vendorAzure AI Document Intelligence pricing — prebuilt and custom extraction rates, and the volume tiers. azure.microsoft.com
- vendorGoogle Document AI pricing — processor rates, the per-hour deployed custom processor charge, and per-page billing on multi-page files. cloud.google.com
- officialDigiLocker — the issued-document route that skips most of the pipeline for supported documents. www.digilocker.gov.in
- officialIncome Tax Department — PAN — the PAN format used in the validation step. www.incometax.gov.in
- industryOpen-source OCR projects — PaddleOCR, docTR and Tesseract, and the licence check required on any model pulled with them.
Checked September 2026. Pricing and draft regulation move; the date is part of the claim.
Ask an AI about this page
Opens your assistant with this page as the source, and a question rather than a summary. It will ask what you are building before it answers.
Nothing is sent from here. The link carries only this page’s title and address.