Validate PAN, Aadhaar, GSTIN, IFSC, UPI IDs, mobile numbers, PIN codes, TAN, CIN, LLPIN, Voter IDs and vehicle numbers — instantly, on-device, with no network call and no data leaving your server. One Rust core, bindings for JavaScript, TypeScript, Python and Rust.
New in v0.2.0: Bharat (BH) series vehicle numbers — 22BH1234AA now validates. Read the Vehicle docs →· Full changelog →
npm install indycheckimport { validatePan } from 'indycheck';
const r = validatePan('AAAPL1234C');
console.log(r.ok); // true
console.log(r.parsed); // { entity: 'Individual', entity_code: 'P' }
console.log(r.reason_hi); // 'मान्य प्रारूप।'Try: · · · · · ·
This demo runs 100% in your browser — the value you type is never sent anywhere.Open the full playground →
PAN, Aadhaar, GSTIN, IFSC, UPI, mobile, PIN, TAN, CIN, LLPIN, Voter ID (EPIC) and vehicle numbers.
No API keys, no network calls, no per-check fees. Works in a Lambda, a CLI, or a browser with no internet.
One small Rust core compiled to native, Node and Python. Nothing to audit, nothing to break on install.
Every failure comes with reason_en and reason_hi, so your form errors make sense to every user in India.
Control spaces, hyphens, +91 normalisation and uppercasing per call — strict for APIs, lenient for messy user input.
mask('aadhaar', …) → XXXX XXXX 0124, format('mobile', …) → +91 98765 43210. Display-safe by default.
One implementation, one test suite — the same rules in JavaScript, Python and Rust. No drift between ports.
Validate thousands of rows in milliseconds. Built for CSV imports, signup forms and KYC pre-checks.
generate('pan') gives you a valid-looking value for tests and seed data — clearly labelled TEST DATA ONLY, never a real ID.
Every result returns a stable error code, a parsed breakdown (state, holder type, bank code…) and the normalised value.
Ready-made Zod schemas, React Hook Form / Yup resolvers and Pydantic types — drop indycheck into the form library you already use.
indycheck validate, detect and generate — check a single value or a whole CSV from your terminal.
| ID | Format | Checksum | Example |
|---|---|---|---|
| PAN | 5 letters + 4 digits + 1 letter | Format only — entity reported, not enforced | AAAPL1234C |
| Aadhaar | 12 digits, first digit 2–9 | Verhoeff | 234567890124 |
| GSTIN | 15 chars: state + PAN + entity + Z + check | Mod-36 Luhn | 27AAAPL1234C1ZE |
| IFSC | 4-letter bank code + 0 + 6 chars | Format only — no public checksum | SBIN0001234 |
| UPI ID | handle@bank | Format only — no public checksum | 9876543210@paytm |
| Mobile | 10 digits starting 6–9 (+91 ok) | Format only — no public checksum | 9876543210 |
| PIN code | 6 digits, first digit 1–9 | Format only — no public checksum | 781001 |
| TAN | 4 letters + 5 digits + 1 letter | Format only — no public checksum | MUMB12345C |
| CIN | 21 chars: L/U + industry + state + year + type + no. | Format only — no public checksum | U12345DL2025PTC123456 |
| LLPIN | 3 letters + 4 digits (AAA-1234) | Format only — no public checksum | AAA-1234 |
| Voter ID (EPIC) | 3 letters + 7 digits | Format only — no public checksum | ABC1234567 |
| Vehicle | State format (8–10 chars) or Bharat (BH) series YYBH####XX | Format only — no public checksum | DL01AB1234 · 22BH1234AA |
| Code | Means | Example |
|---|---|---|
VALID | Fully valid | AAAPL1234C |
INVALID_FORMAT | Characters or pattern don't match | ABCDE1234 as PAN |
INVALID_LENGTH | Wrong length | AAAPL123C as PAN |
INVALID_CHECKSUM | Verhoeff / Mod-36 Luhn check failed | 234567890123 as Aadhaar |
INVALID_FIRST_DIGIT | First digit not allowed | 5123456789 as mobile |
INVALID_STATE_CODE | Unknown state code | 99AAAPL1234C1ZE as GSTIN |
INVALID_HANDLE | UPI handle missing or unknown | rahul@ |
UNKNOWN_TYPE | No known ID format fits (detect only) | hello world |
Branch on code, never on the reason text. Every failure also carries a Hindi reason (reason_hi). Full error-code table →
indycheck validates format and checksum only. It does not — and cannot — tell you whether an ID is real, active, or belongs to the person who typed it.
XXXX XXXX 0124).