Guide · Node.js · SDK · REST API

GSTIN Validation in Node.js — Indian Tax ID Structure Explained

India's Goods and Services Tax Identification Number is a 15-character alphanumeric identifier with embedded state codes, PAN references, and a check character. Here's how to validate it properly — and why a single regex is not enough.

1. What is a GSTIN?

The Goods and Services Tax Identification Number (GSTIN) is the unique tax identifier assigned to every business registered under India's GST regime. GST registration is mandatory for businesses with an annual turnover exceeding ₹20 lakh (₹10 lakh for special category states). Every inter-state transaction, e-commerce seller, and input-tax-credit claim requires a valid GSTIN.

A GSTIN is a 15-character alphanumeric string — not a random code, but a structured identifier that encodes the state of registration, the PAN (Permanent Account Number) of the entity, an entity serial number, and a check character. For example: 27AAPFU0939F1ZV.

If your Node.js application handles Indian B2B invoicing, GST filing, or supply-chain compliance, you need to validate GSTINs — and format-checking alone will not cut it.


2. GSTIN anatomy — 15 characters decoded

Every GSTIN follows the same structure. Let's break down 27AAPFU0939F1ZV:

PositionCharactersMeaningExample
1–22 digitsState code (01–37, matching Indian state/UT codes)27
3–1210 alphanumericPAN of the entity (Permanent Account Number)AAPFU0939F
131 alphanumericEntity number (1–9 for multiple registrations under same PAN, Z as default)1
141 characterDefault character — always ZZ
151 alphanumericCheck character (mod-36 based checksum)V

State codes

The first two digits identify the state or union territory. Here are some common ones:

CodeState / UTCodeState / UT
01Jammu & Kashmir19West Bengal
02Himachal Pradesh21Odisha
06Haryana23Madhya Pradesh
07Delhi24Gujarat
08Rajasthan27Maharashtra
09Uttar Pradesh29Karnataka
10Bihar32Kerala
12Arunachal Pradesh33Tamil Nadu
18Assam36Telangana
37Andhra Pradesh
ℹ️The 4th character of the embedded PAN (position 6 in the GSTIN) encodes the entity type: C = Company, P = Person, F = Firm, H = HUF, A = AOP, T = Trust, and others. This lets you derive the entity type directly from the GSTIN.

3. The check character algorithm

The 15th character of a GSTIN is a check character computed using a mod-36 algorithm. This is similar in concept to Luhn but operates over the full alphanumeric character set (0–9, A–Z = 36 characters). The algorithm works as follows:

  1. Take the first 14 characters of the GSTIN. Map each character to its position in the set 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ (0–35).
  2. For each character at position i (1-indexed), multiply its code-point value by the position factor. Compute value * (i), then split the product into quotient and remainder when divided by 36.
  3. Sum all the (quotient + remainder) pairs across 14 characters.
  4. Compute remainder = total % 36, then the check value is (36 - remainder) % 36.
  5. Map the check value back to a character (0–9 stay as digits, 10–35 become A–Z).

Here is a reference implementation in JavaScript:

// Reference implementation — DO NOT use in production without thorough testing
const CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

function gstinCheckCharacter(gstin14) {
  let total = 0;

  for (let i = 0; i < 14; i++) {
    const charIndex = CHARSET.indexOf(gstin14[i].toUpperCase());
    if (charIndex === -1) return null; // invalid character

    const product = charIndex * (i + 1);
    const quotient = Math.floor(product / 36);
    const remainder = product % 36;
    total += quotient + remainder;
  }

  const mod = total % 36;
  const checkValue = (36 - mod) % 36;
  return CHARSET[checkValue];
}

// Example
gstinCheckCharacter('27AAPFU0939F1Z'); // → "V"
gstinCheckCharacter('29AABCT1332L1Z'); // → "1"
⚠️The exact algorithm used by GSTN has subtle implementation details. Different sources describe slightly different weighting schemes. For production use, rely on the API rather than hand-rolling the check digit — the government portal is the source of truth.

4. Why manual validation isn't enough

State code validation

There are 37 valid state/UT codes, but the range is not contiguous — codes like 00, 11, 13, 14, 15, 16, 17, 25, 26, 28, 31, 34, and 35 are either unused or reserved. A naive 01–37 range check will accept invalid codes. You need an explicit allowlist.

PAN structure within the GSTIN

Positions 3–12 must form a valid PAN: five uppercase letters, four digits, one uppercase letter. The 4th letter encodes the taxpayer category (C, P, F, H, A, T, B, L, J, G). Validating this sub-structure requires more than a basic regex.

Entity type derivation

The entity number at position 13 can be 1–9 or a letter (Z for default first registration). Multiple GSTINs can exist under the same PAN in different states. Knowing whether a GSTIN belongs to a firm, company, or individual requires parsing the embedded PAN correctly.

Check character calculation

The mod-36 check character is essential for catching typos and transposition errors. Skipping it means your application will accept structurally plausible but mathematically invalid GSTINs — numbers that were never issued by GSTN.


5. The right solution: one API call

Instead of implementing state-code lookups, PAN sub-validation, and the mod-36 checksum yourself, use the IsValid GSTIN API. A single GET request validates the full structure, verifies the check character, and returns parsed fields — state name, PAN, entity type, and more.

Full
Validation
structure + checksum
<50ms
Response time
instant validation
100/day
Free tier
no credit card

Get your free API key at isvalid.dev. The free tier includes 100 calls per day — enough for most development and low-volume production use.

Full parameter reference and response schema: GSTIN Validation API docs →


6. Node.js code example

Using the IsValid SDK (country-specific namespace) or the native fetch API.

import { createClient } from '@isvalid-dev/sdk';

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY });

// ── Validate a GSTIN ────────────────────────────────────────────────────────

const result = await iv.in.gstin('27AAPFU0939F1ZV');

if (!result.valid) {
  console.log('Invalid GSTIN');
} else {
  console.log(`Valid GSTIN from ${result.stateName}`);
  console.log(`PAN: ${result.pan}`);
  console.log(`Entity type: ${result.entityType}`);
}
The SDK uses a country-specific namespace: iv.in.gstin(). This groups all India-specific validators (GSTIN, PAN, Aadhaar, etc.) under a single namespace.

7. cURL example

Validate a GSTIN directly from the terminal:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/in/gstin?value=27AAPFU0939F1ZV"

Test with an invalid GSTIN (wrong check character):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/in/gstin?value=27AAPFU0939F1ZX"

Test with an invalid state code:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/in/gstin?value=99AAPFU0939F1ZV"

8. Understanding the response

Response for a valid GSTIN:

{
  "valid": true,
  "gstin": "27AAPFU0939F1ZV",
  "stateCode": "27",
  "stateName": "Maharashtra",
  "pan": "AAPFU0939F",
  "entityNumber": "1",
  "entityType": "Firm",
  "checkChar": "V"
}
FieldTypeDescription
validbooleanWhether the GSTIN is structurally valid with correct check character
gstinstringThe normalized (uppercased) GSTIN
stateCodestring2-digit Indian state or union territory code
stateNamestringFull name of the state or UT (e.g. "Maharashtra")
panstringEmbedded 10-character PAN extracted from the GSTIN
entityNumberstringRegistration serial number under this PAN (1–9 or Z)
entityTypestringDerived from PAN — Company, Person, Firm, HUF, AOP, Trust, etc.
checkCharstringThe 15th check character

When the GSTIN is invalid, valid is false and the parsed fields may be absent or partial depending on which part of the structure failed validation.


9. Edge cases

(a) Multiple GSTINs per PAN

A single business entity (identified by PAN) can hold multiple GSTIN registrations — one per state where it operates. For example, a company registered in Maharashtra and Karnataka will have two GSTINs that share the same PAN (positions 3–12) but differ in the state code (positions 1–2) and entity number (position 13). Do not assume a PAN maps to a single GSTIN.

// Same PAN, different states
const mh = await iv.in.gstin('27AAPFU0939F1ZV'); // Maharashtra
const ka = await iv.in.gstin('29AAPFU0939F1ZB'); // Karnataka

console.log(mh.pan === ka.pan); // true — same entity, different states

(b) State code changes — Telangana

When Telangana was carved out of Andhra Pradesh in 2014, it received state code 36. Andhra Pradesh retained code 37. Legacy GSTINs from unified AP may still circulate. Your validation logic should accept code 36 (Telangana) as a valid, distinct state — not treat it as an anomaly.

(c) Input normalization

Users may enter GSTINs with spaces, hyphens, or lowercase letters: 27 AAPF U0939F 1ZV, 27-AAPFU0939F-1ZV, or 27aapfu0939f1zv. The API strips whitespace and hyphens, and uppercases the input automatically. Pass the raw user input — no need to sanitize beforehand.

// All of these produce the same result
await iv.in.gstin('27AAPFU0939F1ZV');
await iv.in.gstin('27 aapfu0939f 1zv');
await iv.in.gstin('27-AAPFU0939F-1ZV');

Network failures in your code

Always wrap the API call in a try/catch. A network timeout should not cause your invoice flow to crash — decide upfront whether to fail open or closed on API unavailability.

async function validateGstinSafe(gstin) {
  try {
    return await iv.in.gstin(gstin);
  } catch (err) {
    // Log and decide: fail open (allow) or fail closed (reject)
    logger.error('GSTIN validation failed:', err.message);
    return { valid: null, error: 'validation_unavailable' };
  }
}

10. Summary

Do not rely on a basic regex for GSTIN — it misses state codes and check characters
Do not skip the mod-36 check character — it catches typos and transpositions
Validate the full structure: state code + PAN + entity number + check character
Use the parsed response to extract PAN, state, and entity type automatically
Handle multiple GSTINs per PAN across different states
Let the API normalize input — no need to strip spaces or uppercase manually

See also

Validate GSTIN numbers instantly

Free tier includes 100 API calls per day. No credit card required. Full structure validation with parsed state, PAN, and entity type.