Indian Business Compliance — GSTIN Validation
India's GST system uses 15-character GSTIN numbers that encode the state, PAN, entity type, and a checksum. Here's how to validate GSTINs, extract embedded business metadata, and build a GST compliance check into your onboarding flow.
In this guide
1. What is GSTIN and why validate it
GSTIN (Goods and Services Tax Identification Number) is a 15-character alphanumeric identifier assigned to every registered GST taxpayer in India. It was introduced with India's landmark GST reform of 2017, replacing a fragmented state-level tax system with a unified national system.
Any business with annual turnover above ₹40 lakh (₹20 lakh for services in most states) must register and collect GST. When onboarding Indian business partners, vendors, or customers, validating their GSTIN is essential for:
- Input Tax Credit (ITC) eligibility — you can only claim ITC against invoices from GST-registered suppliers with valid GSTINs
- GST Rule 36(4) compliance — limits ITC to suppliers who have filed their GSTR-1 returns
- Fraud prevention — fake GSTINs are used in GST invoice fraud; validation catches structural anomalies
- Vendor due diligence — cross-referencing the embedded PAN confirms the legal entity identity
2. The 15-character GSTIN structure
Every GSTIN encodes several pieces of business information:
| Position | Characters | Meaning | Example |
|---|---|---|---|
| 1–2 | 27 | State code (01–37) | 27 = Maharashtra |
| 3–12 | AAPFU0939F | PAN of the registered entity | 10-character PAN |
| 13 | 1 | Entity number (1–9, A–Z) | Multiple registrations in same state |
| 14 | Z | Default 'Z' | Always Z in current format |
| 15 | V | Check character (alphanumeric) | Weighted checksum |
Example: 27AAPFU0939F1ZV = Maharashtra (27) + PAN AAPFU0939F + entity 1 + Z + check V.
3. Validating with the API
The endpoint validates the full 15-character GSTIN and extracts the embedded metadata:
Example response
{ "valid": true, "gstin": "27AAPFU0939F1ZV", "stateCode": "27", "stateName": "Maharashtra", "pan": "AAPFU0939F", "entityNumber": "1", "entityType": "Firm", "checkChar": "V" }
4. What to check in your code
valid === true — the check character and overall format pass validation
stateCode and stateName — verify the registration state matches the supplier's declared operating state
pan — the embedded PAN identifies the legal entity; cross-reference with the provided PAN for KYC
entityType — "Firm", "Individual", "Company", etc. for KYC categorization and appropriate verification workflow
5. Code example — single and batch validation
import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); async function validateGSTIN(gstin) { const result = await iv.gstin(gstin); return { valid: result.valid, stateCode: result.stateCode ?? null, stateName: result.stateName ?? null, pan: result.pan ?? null, entityType: result.entityType ?? null, entityNumber: result.entityNumber ?? null, checkChar: result.checkChar ?? null, }; } // ── Single validation ─────────────────────────────────────────────────────── const vendor = await validateGSTIN('27AAPFU0939F1ZV'); if (!vendor.valid) { console.error('Invalid GSTIN — reject invoice, do not claim ITC'); } else { console.log(`✓ ${vendor.stateName} | PAN: ${vendor.pan} | Entity: ${vendor.entityType}`); } // ── Batch validation for ITC reconciliation ───────────────────────────────── const vendorGSTINs = [ '27AAPFU0939F1ZV', // Maharashtra '29GGGGG1314R9Z6', // Karnataka '19AABCT3518Q1ZV', // West Bengal ]; const results = await Promise.all( vendorGSTINs.map(gstin => validateGSTIN(gstin)) ); results.forEach((r, i) => { const gstin = vendorGSTINs[i]; if (!r.valid) { console.log(`GSTIN ${gstin}: ✗ INVALID — withhold ITC`); } else { console.log(`GSTIN ${gstin}: ✓ ${r.stateName} | ${r.entityType} | PAN: ${r.pan}`); } });
Promise.all — validates the entire vendor list in a single parallel round-trip.6. cURL example
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/gstin?value=27AAPFU0939F1ZV"
7. Handling edge cases
State code mismatch
Supplier claims to be in Maharashtra (27) but the GSTIN starts with 29 (Karnataka). This could indicate the supplier operates from a different state — verify before approving.
if (gstinResult.valid && gstinResult.stateCode !== expectedStateCode) { logger.warn(`State mismatch: GSTIN state=${gstinResult.stateName}, declared=${expectedState}`); return { status: 'review_required', reason: 'GSTIN state does not match declared state' }; }
Multiple GSTINs for the same PAN
Large enterprises can have multiple GSTINs for the same PAN (one per state). The entity number (position 13) differentiates them. This is valid and expected.
// Same PAN, different states — all valid for ITC purposes // Detect multiple registrations by grouping by PAN const byPan = results.reduce((acc, r) => { if (r.valid && r.pan) { acc[r.pan] = acc[r.pan] || []; acc[r.pan].push(r.stateCode); } return acc; }, {}); // { 'AAPFU0939F': ['27', '29'] } → same entity, 2 state registrations
Format valid but registration cancelled
GSTIN format validation confirms the checksum, not the active registration status. A structurally valid GSTIN could belong to a cancelled registration. For high-value vendor relationships, verify active status on the GSTN portal.
// API validates format/checksum — for active registration status, // consider periodic re-verification against GSTN portal if (gstinResult.valid) { vendor.gstinVerifiedAt = new Date(); vendor.gstinNextVerify = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // re-check in 30 days }
8. Summary checklist
See also
Validate Indian GST registrations at scale
Free tier includes 100 API calls per day. GSTIN validation with state code and PAN extraction included. No credit card required.