Skip to content

Getting started

indycheck validates 12 Indian IDs — PAN, Aadhaar, GSTIN, IFSC, UPI ID, mobile number, PIN code, TAN, CIN, LLPIN, Voter ID (EPIC) and vehicle numbers — offline. No API keys, no network calls, no per-check fees. One Rust core, with bindings for JavaScript/TypeScript, Python and Rust.

These docs are in English for v0.2.0. The error messages in the package itself are bilingual: every result carries both reason_en and reason_hi.

Terminal window
# JavaScript / TypeScript
npm install indycheck
# Python
pip install indycheck
# Rust
cargo add indycheck

Requires Node 18+, Python 3.9+, or Rust 1.85+. There are zero runtime dependencies in every binding — the Rust core is compiled in.

// JavaScript / TypeScript
import { validatePan } from 'indycheck';
const r = validatePan('AAAPL1234C');
console.log(r.ok); // true
console.log(r.code); // 'VALID'
console.log(r.parsed); // { entity: 'Individual', entity_code: 'P' }
# Python
from indycheck import validate_pan
r = validate_pan("AAAPL1234C")
print(r.ok) # True
print(r.code) # 'VALID'
print(r.parsed) # {'entity': 'Individual', 'entity_code': 'P'}
// Rust
use indycheck::validate_pan;
let r = validate_pan("AAAPL1234C");
assert!(r.ok);
println!("{}", r.code); // VALID

Every validator returns the same shape:

{
"ok": true,
"type": "pan",
"value": "AAAPL1234C",
"normalized": "AAAPL1234C",
"code": "VALID",
"reason_en": "Valid format.",
"reason_hi": "मान्य प्रारूप।",
"parsed": {
"entity": "Individual",
"entity_code": "P"
}
}

And a failure:

{
"ok": false,
"type": "pan",
"value": "AAAPL12345",
"code": "INVALID_FORMAT",
"reason_en": "PAN format must be 5 letters, 4 digits, 1 letter (e.g. ABCDE1234F). PAN has no public checksum, so only the format is checked.",
"reason_hi": "PAN ka format 5 akshar, 4 digit, 1 akshar hona chahiye (jaise ABCDE1234F). PAN ka koi public checksum nahi hai, isliye sirf format check hota hai"
}

Use code for branching in your code, and reason_en / reason_hi for showing errors to users. See API & error codes for the full list.

Function Checks Example
validateMobile 10 digits, starts 6–9 9876543210
validatePan Format only (entity reported, not enforced) AAAPL1234C
validateAadhaar 12 digits + Verhoeff checksum 234567890124
validateGstin Format + Mod-36 Luhn checksum 27AAAPL1234C1ZE
validateIfsc Bank code + 0 + branch code SBIN0001234
validateUpi handle@bank format 9876543210@paytm
validatePincode 6 digits, first digit 1–9 781001
validateTan 4 letters + 5 digits + 1 letter, format only MUMB12345C
validateCin 21-char company structure, format only U12345DL2025PTC123456
validateLlpin 3 letters + 4 digits, format only AAA-1234
validateEpic 3 letters + 7 digits, format only ABC1234567
validateVehicle State format (8–10 chars) or Bharat (BH) series YYBH####XX, format only DL01AB1234 / 22BH1234AA

TAN, CIN, LLPIN, Voter ID and vehicle numbers have no public checksum — for those, indycheck checks the format only. (IFSC, UPI, mobile and PIN code are format-only too.)

There is also a generic entry point — validate('pan', value) — handy when the type comes from user input, plus detect(value) to guess the type, generate(type) for test data, and mask() / format() for display. Try everything live in the playground.

Validators are pure functions with no I/O, so batching is just a loop:

import { validate } from 'indycheck';
const rows = ['AAAPL1234C', 'AAAPL12345', '9876543210'];
const results = rows.map((v) => validate('pan', v));
const bad = results.filter((r) => !r.ok);
console.log(`${bad.length} of ${rows.length} failed`);