This sheet names the tools. Getting Access → tells you how to sign up for each one, and which need a licence.
How to read this build sheet
The module next door explains how identity verification works. This page is the parts list and the assembly instructions.
Five sections, in build order:
- Raw materials — every tool that can do this job
- How to use each one — what you send, what comes back, what to check, the first working call, the gotcha, the cost
- Best combinations — what works together and what conflicts
- Three recommended builds — the same job at three grades
- What next — the module this feeds
Every vendor link on this page is there so you can verify what we say. You should not need to visit any of them to understand how the tool works — that is on this page. If you do, tell us, because the page has failed.
Prices and API shapes carry a Verified May 2026 stamp and sit in clearly marked blocks. The integration shape and the combinations hold for years. The price per call does not. When this page ages, it will be obvious which part aged.
The five jobs, and what each needs
| Job | Material type | Can you skip it? |
|---|---|---|
| 1. Capture a usable image | Mobile SDK or web capture + quality gate | No. Bad input ruins everything downstream. |
| 2. Read the document | OCR engine or document API | No. |
| 3. Structure the fields | Layout model, LLM, or a prebuilt ID parser | No. |
| 4. Prove the person is live and matches | Face match + liveness | Only for low-risk, non-regulated products. |
| 5. Verify against the issuing source | Licensed KYC provider | No, and in India this is indirect-only. |
Jobs 1 to 4 you can assemble yourself. Job 5 you cannot ’ which is why the recommended builds all route through a licensed provider, whatever you do above it.
Raw materials — document reading
| Material | What it does | Verify at |
|---|---|---|
| Tesseract 5 | Classic OCR. Small, fast, free. Weak on Indic scripts and angled captures. | github.com/tesseract-ocr |
| PaddleOCR | Deep-learning OCR with layout analysis. Strong multilingual. | github.com/PaddlePaddle |
| docTR | Detection + recognition in one pipeline, tunable. | github.com/mindee/doctr |
| Surya | Best open layout analysis, 90+ languages. Licence needs checking. | github.com/datalab-to/surya |
| Qwen2.5-VL / olmOCR | Vision-language OCR. Reads contextually; can hallucinate. | huggingface.co/Qwen |
| AWS Textract | Managed. Has a dedicated AnalyzeID for identity documents. | aws.amazon.com/textract |
| Google Document AI | Managed, processor-based, includes prebuilt ID parsers. | cloud.google.com/document-ai |
| Azure Document Intelligence | Managed, prebuilt ID models, cheapest classifier tier. | azure.microsoft.com |
| Mistral OCR | LLM-based document reading API, aggressive pricing. | mistral.ai |
Raw materials — identity verification and biometrics (India)
| Material | What it does | Verify at |
|---|---|---|
| UIDAI Aadhaar eKYC | The issuing source. Indirect only — via a licensed KUA/KSA. | uidai.gov.in |
| DigiLocker | Government-issued documents pulled with consent. | digilocker.gov.in |
| Signzy | Full onboarding orchestration, video KYC, CKYCR, DigiLocker. | signzy.com |
| HyperVerge | Face match and liveness trained on Indian faces and low-end devices. | hyperverge.co |
| IDfy | OCR plus video KYC; bundles Aadhaar, PAN and bank checks. | idfy.com |
| Karza / Perfios | Identity plus financial verification in one stack. | perfios.com |
| Surepass | Very broad verification API catalogue, fast sandbox. | surepass.io |
| Digio | Strongest on Aadhaar eSign and eStamp. | digio.in |
| iProov | High-assurance liveness, government-grade. | iproov.com |
| InsightFace | Open-source face recognition. You own the bias testing. | github.com/deepinsight |
How to use each one — document reading
AWS Textract AnalyzeID
What it is for: the one Textract API built specifically for identity documents. Do not use AnalyzeDocument for an ID card — it costs several times more and returns generic key-value pairs instead of named identity fields.
import boto3
client = boto3.client("textract", region_name="ap-south-1")
with open("id_front.jpg", "rb") as f:
img = f.read()
resp = client.analyze_id(DocumentPages=[{"Bytes": img}])
# WHAT COMES BACK: one IdentityDocument per page, each with
# IdentityDocumentFields = list of {Type, ValueDetection}
out = {}
for doc in resp["IdentityDocumentFields"] if "IdentityDocumentFields" in resp \
else resp["IdentityDocuments"][0]["IdentityDocumentFields"]:
key = doc["Type"]["Text"] # e.g. FIRST_NAME, DATE_OF_BIRTH
val = doc["ValueDetection"]["Text"]
conf = doc["ValueDetection"]["Confidence"] # 0-100, NOT 0-1
out[key] = {"value": val, "confidence": conf}
# WHAT TO CHECK, in this order:
# 1. confidence per FIELD, not per document
# 2. DATE_OF_BIRTH parsed to a real date - the string can be anything
# 3. ID_NUMBER against a checksum (Verhoeff for Aadhaar - see the module)
# 4. that every field you rely on is actually present; missing != emptyThe gotcha: confidence is returned on a 0–100 scale here and 0–1 in most other APIs. Teams mix the two and end up with a threshold of 0.95 that passes everything.
Azure Document Intelligence — prebuilt ID
from azure.ai.documentintelligence import DocumentIntelligenceClient
from azure.core.credentials import AzureKeyCredential
client = DocumentIntelligenceClient(ENDPOINT, AzureKeyCredential(KEY))
with open("id_front.jpg", "rb") as f:
poller = client.begin_analyze_document("prebuilt-idDocument", body=f)
result = poller.result()
# WHAT COMES BACK: documents[] -> fields{} keyed by name,
# each with .value_string / .value_date and .confidence (0-1 here)
doc = result.documents[0]
first = doc.fields.get("FirstName")
dob = doc.fields.get("DateOfBirth")
out = {
"first_name": first.value_string if first else None,
"first_conf": first.confidence if first else 0.0,
"dob": dob.value_date.isoformat() if dob and dob.value_date else None,
"doc_type": doc.doc_type, # tells you WHICH id it thinks it is
}
# WHAT TO CHECK:
# - doc_type first. If it classified a PAN card as a passport,
# every field mapping below it is wrong.
# - value_date is already parsed - prefer it over value_string
# - confidence is 0-1 here (contrast with Textract's 0-100)The gotcha: the free tier caps at 2 pages per document, which quietly truncates anything longer and gives you a clean-looking result from an incomplete read.
Self-hosted: PaddleOCR + LLM structuring
from paddleocr import PaddleOCR
import json, re
ocr = PaddleOCR(use_angle_cls=True, lang="en") # add 'devanagari' etc as needed
def read(path):
res = ocr.ocr(path, cls=True)
lines = []
for block in res:
for (_box, (text, conf)) in block:
lines.append({"text": text, "conf": float(conf)}) # conf 0-1
return lines
# WHAT COMES BACK: a flat list of text lines with confidences and boxes.
# NO field names. Structuring is your job - see the module for the
# LLM prompt and the grounding check that stops invented values.
def to_text(lines, min_conf=0.60):
return "\n".join(l["text"] for l in lines if l["conf"] >= min_conf)
# WHAT TO CHECK:
# - drop low-confidence lines BEFORE structuring, or the LLM will
# faithfully structure garbage
# - keep the raw lines: they are your grounding corpus for verifying
# whatever the LLM returnsThe gotcha: PaddleOCR downloads models on first run. In a container with no egress that fails at startup rather than at build, so bake the models into the image.
How to use each one — verification and liveness
Indian KYC providers — the shape they all share
These vendors differ in coverage and pricing, not in shape. Nearly all of them look like this, so learning one transfers.
1. AUTHENTICATE
POST /auth {client_id, client_secret} -> {token, expires_in}
Cache the token. Re-authenticating per request is the most common
cause of rate-limit errors in the first week.
2. INITIATE (for flows needing consent or a redirect)
POST /kyc/initiate
{customer_ref, purpose, redirect_url, consent_text}
-> {request_id, redirect_url}
Store request_id. Everything afterwards is keyed on it.
3. RESULT (webhook is authoritative; polling is a fallback)
POST your /webhook <- the vendor calls YOU
{request_id, status, data{...}, signature}
GET /kyc/status/{request_id} <- poll only if the webhook is late
4. WHAT TO CHECK, every time:
[ ] verify the webhook SIGNATURE before trusting the body
[ ] status is an ENUM - handle 'pending' and 'manual_review',
not just success/failure
[ ] match the returned name against the name you submitted;
a "success" can still be a different person
[ ] store the vendor's transaction reference - you will need it
to answer "prove this customer was verified"
[ ] treat the raw response as evidence: store it, do not just
parse and discardThe gotcha nobody documents: a successful verification response means the lookup succeeded, not the person is who they claim. Name matching between what you submitted and what came back is your job, and it is where fraud passes through.
Face match and liveness
CLIENT SIDE (mobile SDK or web)
1. SDK captures - you never receive a raw upload path the user controls
2. SDK returns a session token / encrypted payload, NOT an image
-> if a vendor lets you POST an arbitrary image for liveness,
that is not liveness. It is face matching on a file.
SERVER SIDE
POST /liveness/verify {session_token}
-> {liveness: pass|fail, score, face_match_score, session_id}
WHAT TO CHECK
[ ] device attestation passed (Play Integrity / App Attest)
-> this is what stops CAMERA INJECTION, the attack that
defeats passive AND active liveness
[ ] liveness and face_match are SEPARATE decisions with separate
thresholds. A live person who is not the document holder
passes liveness and must fail match.
[ ] face_match is a band, not a line:
> 0.80 accept
0.60-0.80 human review
< 0.60 reject
Calibrate on YOUR user base. Published thresholds are tuned
on datasets that do not look like your customers.
[ ] store session_id - the vendor holds the recording and you will
need to retrieve it during a disputeTest liveness on the devices your customers actually use. Models trained on Western datasets underperform on Indian skin tones, low-light interiors and inexpensive Android cameras — the difference shows up as false rejections of genuine customers in tier-2 and tier-3 towns, which looks like poor conversion rather than a model problem.
Cost per unit
Document reading — per 1,000 pages
Verified May 2026Features stack, and one document can hit several meters. A non-standard document typically runs through a classifier (~$3) and then custom extraction (~$30) — so the real per-document cost is the sum of every model that fires, not a single headline rate. This is the single most common mistake in a document-AI cost estimate.
Two more that 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.
Best combinations
| Combination | Works because |
|---|---|
| Quality gate → classifier → prebuilt ID parser → checksum | Cheapest path per document. The gate removes bad captures before you pay, the classifier routes to the right parser, the checksum catches what confidence misses. |
| Open OCR → LLM structuring → grounding check | No per-page fee, works under data residency, and the grounding check neutralises hallucination. See the module for that code. |
| India-tuned liveness + separate face-match threshold | Liveness answers "is this a real person"; match answers "is it the right one". Conflating them lets a real person onboard as someone else. |
| Any extraction + licensed source verification | Extraction proves what the card says. Only the issuing database proves the card is real. |
Combinations that conflict
- VLM extraction without a grounding check. Fluent invented names, no visible failure.
- Cloud document API + hard data residency. Inference is processing. Check before building.
- Web-only capture + high-risk product. No device attestation means no defence against camera injection.
- Textract AnalyzeDocument on ID cards. Several times the price of AnalyzeID for a worse result.
- Global face models on Indian tier-2/3 users. Silent conversion loss.
Three recommended builds
Strong and expensive
Build: Native mobile SDK from a licensed Indian KYC provider handling capture, extraction, liveness, face match and source verification end to end. One vendor, one contract.
Use when: regulated product, funded, launching inside six months, small team.
Cost shape: highest per verification; lowest engineering cost.
Trade: you own almost none of it. Switching later means re-verifying customers.
Strong and reasonable — the default
Build: Your own quality gate and preprocessing → cloud prebuilt ID parser for extraction → your own validation and checksums → India-tuned liveness vendor → licensed provider for source verification only.
Use when: you have engineers and want control of the parts that decide cost and conversion.
Cost shape: pay per page for extraction, per verification for the licensed step, nothing for the gate and validation you own.
Trade: more integration work, and you own the thresholds — which is the point.
This is the recommended build for most fintechs. The quality gate and the validation layer are the two pieces that most affect both cost and conversion, and they are the two you can own outright without a licence.
Strong and lean
Build: Self-hosted PaddleOCR or docTR → LLM structuring with a grounding check → open-source face matching → licensed provider for source verification.
Use when: hard data residency, high volume, ML capability in house.
Cost shape: no per-page fee; you pay infrastructure and engineering.
Trade: you own accuracy, bias testing and uptime. The licensed verification step is still unavoidable.
All three route the final step through a licensed provider, because Aadhaar eKYC is indirect-only regardless of what you build above it. Any design that assumes direct access is not buildable in India.
What next
Onboarding produces a verified customer. Three things follow immediately:
- Screen them — sanctions and PEP checks on the name you just verified. This happens before the account is usable, not after.
- Assess them — if you are lending. The identity is settled; capacity and willingness are not.
- Watch them — a real person who passed every check can still be a mule. Onboarding cannot catch that; behaviour can.
And one that runs alongside: every model you just wired in — the extraction model, the face-match model, the liveness vendor — belongs in the model inventory with an owner, a tier and a kill switch. Including the ones you bought.
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 — the ~$1.50 per 1,000 plain OCR rate and the AnalyzeID band. aws.amazon.com
- vendorAzure AI Document Intelligence pricing — prebuilt and custom model rates and the volume tiering. azure.microsoft.com
- officialUIDAI — Aadhaar verification routes and the checksum used in validation. uidai.gov.in
- officialRBI Master Direction on KYC — V-CIP requirements and the record-keeping obligations. www.rbi.org.in
- industryOpen-source OCR licensing — the research-only restrictions that apply to some models distributed with otherwise permissive tooling.
Checked May 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.