Check a GSTIN or PAN before you save it
Catch typing mistakes in GSTINs and PANs with the check character, and read the state and PAN out of a GSTIN, in JavaScript.
A GSTIN has fifteen characters: a two-digit state code, the holder's PAN, an entity number, the letter Z and a check character computed from the other fourteen. One wrong character makes the check fail, so a format check catches nearly every typing mistake without asking any government system.
The request
curl https://api.dharun.dev/v1/validate/gstin/27AAPFU0939F1ZV
{
"data": {
"kind": "gstin",
"normalized": "27AAPFU0939F1ZV",
"valid": true,
"parts": {
"entityNumber": "1",
"holderType": "Firm or Limited Liability Partnership",
"pan": "AAPFU0939F",
"state": "Maharashtra",
"stateCode": "27"
}
},
"meta": { "note": "This checks the format only. It does not confirm that the identifier was ever issued." }
}
A wrong character still answers 200, with valid: false and the reason:
curl https://api.dharun.dev/v1/validate/gstin/27AAPFU0939F1ZX
{ "data": { "kind": "gstin", "normalized": "27AAPFU0939F1ZX", "valid": false,
"reason": "The check character does not match; one of the characters is wrong." } }
In a form
async function checkGstin(value) {
const response = await fetch(
`https://api.dharun.dev/v1/validate/gstin/${encodeURIComponent(value.trim())}`
);
const { data } = await response.json();
if (!data.valid) return { ok: false, message: data.reason };
return {
ok: true,
gstin: data.normalized, // upper case, spaces removed: store this one
state: data.parts.state,
pan: data.parts.pan,
};
}
const result = await checkGstin("27aapfu0939f1zv");
console.log(result.ok ? `${result.gstin} is from ${result.state}` : result.message);
Compare state with the billing address's state: a GSTIN registered in another state is a common source of wrong tax on an invoice.
PAN
The fourth letter of a PAN says what kind of holder it belongs to: P for a person, C for a company, F for a firm and so on.
curl https://api.dharun.dev/v1/validate/pan/ABCPK1234F
{ "data": { "kind": "pan", "normalized": "ABCPK1234F", "valid": true, "parts": { "holderType": "Individual" } } }
What it cannot tell you
These checks are structural. A well-formed GSTIN may never have been issued, or may have been cancelled; only the GST portal knows that. Values sent to the format checks are never written to the request log.
The code in this guide is MIT-0: copy it into anything, no credit needed. Updated 2026-10-14.
APIs used: Format checks, States.