GSTIN Verification API: How to Validate GST Numbers Programmatically
Complete guide to validating GSTIN numbers via API — format rules, checksum validation, real-time GST Network verification, and integration code samples.
Every B2B transaction in India's GST system involves GSTIN validation. Whether you are building an invoicing platform, onboarding vendors, processing purchase orders, or filing GST returns, verifying that a GSTIN is correctly formatted, currently active, and belongs to the claimed entity is a non-negotiable compliance requirement. Under Section 16(2)(a) of the CGST Act 2017, Input Tax Credit can only be claimed if the supplier is registered — making GSTIN verification a direct financial control.
This guide covers the complete GSTIN verification workflow: format structure, offline validation, real-time API verification, integration code samples, and production deployment patterns.
What Is the Structure of a GSTIN Number?
A Goods and Services Tax Identification Number (GSTIN) is a 15-character alphanumeric identifier assigned to every registered taxpayer under India's GST system. Understanding its structure is the foundation of both offline format validation and interpreting verification results.
The 15 characters encode specific information about the taxpayer's location, identity, and registration type. This structured format means you can extract meaningful data from a GSTIN even before making a verification API call.
| Position | Characters | Meaning | Valid Values | Example |
|
|
|
|
|
|
| 1-2 | 2 digits | State/UT code | 01-37 (per Census of India) | 27 (Maharashtra) |
| 3-7 | 5 uppercase letters | First 5 chars of PAN | A-Z | AABCU |
| 8-11 | 4 digits | Next 4 chars of PAN | 0-9 | 9603 |
| 12 | 1 uppercase letter | Last char of PAN | A-Z | R |
| 13 | 1 alphanumeric | Entity number (registrations per PAN in state) | 1-9, A-Z | 1 |
| 14 | 1 letter | Default/reserved | Always 'Z' | Z |
| 15 | 1 alphanumeric | Check digit | Calculated via Luhn mod 36 | M |
Full example: `27AABCU9603R1ZM`The PAN embedded at positions 3-12 is itself structured: the 4th character indicates the entity type — C for Company, P for Person, H for HUF, F for Firm, A for AOP, T for Trust, B for BOI, L for Local Authority, J for Artificial Juridical Person, and G for Government.
The state codes follow the Census of India state numbering. Key codes include: 01 (Jammu & Kashmir), 07 (Delhi), 09 (Uttar Pradesh), 27 (Maharashtra), 29 (Karnataka), 33 (Tamil Nadu), 36 (Telangana).
How Do You Validate GSTIN Format Offline?
Before making any API call, validate the GSTIN format locally. This catches typos, truncated inputs, and obviously invalid values — saving API costs and reducing latency for the 5-10% of inputs that are malformed.
Local validation involves four checks executed in sequence. If any check fails, the GSTIN is invalid and does not need API verification.
Step 1: Length and character set. The GSTIN must be exactly 15 characters, containing only uppercase letters (A-Z) and digits (0-9). Reject any input with lowercase letters, special characters, or incorrect length. Step 2: State code validation. Positions 1-2 must form a valid Indian state or union territory code. The valid range is 01 through 37, but not all numbers in this range are assigned. Maintain a lookup table of valid codes. Step 3: PAN format validation. Positions 3-12 must conform to the PAN format: 5 letters, 4 digits, 1 letter. The 4th character of the PAN (position 6 of GSTIN) must be one of the valid entity type codes (C, P, H, F, A, T, B, L, J, G). Step 4: Check digit verification. Position 14 must be 'Z'. Position 15 must match the check digit computed using the Luhn mod 36 algorithm over positions 1-14.Here is the complete implementation:
```python
def validate_gstin_format(gstin: str) -> tuple[bool, str]:
"""Validate GSTIN format offline without API call.
Returns:
Tuple of (is_valid, error_message). error_message is empty if valid.
"""
if not gstin or len(gstin) != 15:
return False, f"GSTIN must be 15 characters, got {len(gstin) if gstin else 0}"
gstin = gstin.upper().strip()
# Check character set
if not gstin.isalnum():
return False, "GSTIN must contain only letters and digits"
# Validate state code (positions 1-2)
valid_state_codes = {
"01", "02", "03", "04", "05", "06", "07", "08", "09", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
"21", "22", "23", "24", "25", "26", "27", "28", "29", "30",
"31", "32", "33", "34", "35", "36", "37",
}
state_code = gstin[:2]
if state_code not in valid_state_codes:
return False, f"Invalid state code: {state_code}"
# Validate PAN format (positions 3-12)
pan = gstin[2:12]
if not (pan[:5].isalpha() and pan[5:9].isdigit() and pan[9].isalpha()):
return False, f"Invalid PAN format embedded in GSTIN: {pan}"
# Validate entity type (4th char of PAN = position 6 of GSTIN)
valid_entity_types = set("CPHFATBLJG")
if pan[3] not in valid_entity_types:
return False, f"Invalid entity type in PAN: {pan[3]}"
# Position 14 must be 'Z'
if gstin[13] != "Z":
return False, f"Position 14 must be 'Z', got '{gstin[13]}'"
# Validate check digit (position 15) using Luhn mod 36
if not _verify_check_digit(gstin):
return False, "Check digit validation failed"
return True, ""
def _verify_check_digit(gstin: str) -> bool:
"""Verify GSTIN check digit using Luhn mod 36 algorithm."""
char_map = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
factor = 1
total = 0
for i in range(14): # Process first 14 characters
code = char_map.index(gstin[i])
digit = factor * code
digit = (digit // 36) + (digit % 36)
total += digit
factor = 2 if factor == 1 else 1
remainder = total % 36
check_code = (36 - remainder) % 36
expected_check = char_map[check_code]
return gstin[14] == expected_check
# Usage
is_valid, error = validate_gstin_format("27AABCU9603R1ZM")
print(f"Valid: {is_valid}") # True
is_valid, error = validate_gstin_format("99XXXXX0000X0Z0")
print(f"Valid: {is_valid}, Error: {error}") # Invalid state code
```
Node.js implementation:```javascript
function validateGSTINFormat(gstin) {
if (!gstin || gstin.length !== 15) {
return { valid: false, error: `GSTIN must be 15 characters, got ${gstin?.length || 0}` };
}
gstin = gstin.toUpperCase().trim();
// Check alphanumeric
if (!/^[A-Z0-9]+$/.test(gstin)) {
return { valid: false, error: "GSTIN must contain only letters and digits" };
}
// State code validation
const stateCode = parseInt(gstin.substring(0, 2), 10);
if (stateCode < 1 || stateCode > 37) {
return { valid: false, error: `Invalid state code: ${gstin.substring(0, 2)}` };
}
// PAN format (positions 3-12)
const pan = gstin.substring(2, 12);
if (!/^[A-Z]{5}[0-9]{4}[A-Z]$/.test(pan)) {
return { valid: false, error: `Invalid PAN format: ${pan}` };
}
// Entity type (4th char of PAN)
if (!"CPHFATBLJG".includes(pan[3])) {
return { valid: false, error: `Invalid entity type: ${pan[3]}` };
}
// Position 14 must be Z
if (gstin[13] !== "Z") {
return { valid: false, error: `Position 14 must be Z, got ${gstin[13]}` };
}
// Check digit verification (Luhn mod 36)
const charMap = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let factor = 1;
let total = 0;
for (let i = 0; i < 14; i++) {
const code = charMap.indexOf(gstin[i]);
let digit = factor * code;
digit = Math.floor(digit / 36) + (digit % 36);
total += digit;
factor = factor === 1 ? 2 : 1;
}
const remainder = total % 36;
const checkCode = (36 - remainder) % 36;
const expectedCheck = charMap[checkCode];
if (gstin[14] !== expectedCheck) {
return { valid: false, error: "Check digit validation failed" };
}
return { valid: true, error: "" };
}
```
How Do You Verify a GSTIN Against the GST Network in Real Time?
Format validation confirms the GSTIN is structurally correct, but it cannot tell you whether the GSTIN actually exists, is currently active, or belongs to the claimed entity. For that, you need real-time verification against the GST Network — the central database maintained by GSTN (Goods and Services Tax Network), an entity authorized by the GST Council.
Real-time verification returns comprehensive taxpayer data including registration status, legal and trade names, principal place of business, date of registration, last return filing date, and the nature of business activities.
cURL — Quick verification```bash
curl -X GET "https://api.anumiti.ai/v1/gstin/verify/27AABCU9603R1ZM" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
Python — Production integration```python
import requests
from datetime import datetime, timedelta
from functools import lru_cache
class GSTINVerifier:
"""GSTIN verification client with caching and validation."""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.anumiti.ai/v1/gstin"
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
})
self._cache = {}
def verify(self, gstin: str, use_cache: bool = True) -> dict:
"""Verify a GSTIN against GST Network.
Args:
gstin: 15-character GSTIN to verify
use_cache: Whether to use cached results (default 24hr TTL)
Returns:
Verification result with status, legal name, and details
"""
gstin = gstin.upper().strip()
# Offline format validation first
is_valid, error = validate_gstin_format(gstin)
if not is_valid:
return {"verified": False, "error": error, "source": "format_check"}
# Check cache
if use_cache and gstin in self._cache:
cached, timestamp = self._cache[gstin]
if datetime.now() - timestamp < timedelta(hours=24):
cached["source"] = "cache"
return cached
# API verification
try:
response = self.session.get(
f"{self.base_url}/verify/{gstin}",
timeout=10,
)
response.raise_for_status()
result = response.json()
result["source"] = "api"
# Cache successful results
self._cache[gstin] = (result, datetime.now())
return result
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
return {"verified": False, "error": "GSTIN not found in GST Network"}
elif e.response.status_code == 429:
return {"verified": None, "error": "Rate limit exceeded, retry later"}
raise
def bulk_verify(self, gstins: list[str]) -> list[dict]:
"""Verify multiple GSTINs in a single batch request."""
# Format-validate all GSTINs first
valid_gstins = []
results = {}
for gstin in gstins:
is_valid, error = validate_gstin_format(gstin.upper().strip())
if is_valid:
valid_gstins.append(gstin.upper().strip())
else:
results[gstin] = {"verified": False, "error": error}
if valid_gstins:
response = self.session.post(
f"{self.base_url}/verify/bulk",
json={"gstins": valid_gstins},
timeout=60,
)
response.raise_for_status()
for item in response.json()["results"]:
results[item["gstin"]] = item
return [results.get(g.upper().strip(), {"error": "Not processed"}) for g in gstins]
# Usage
verifier = GSTINVerifier(api_key="your_key_here")
result = verifier.verify("27AABCU9603R1ZM")
if result.get("verified"):
print(f"Legal Name: {result['data']['legal_name']}")
print(f"Trade Name: {result['data']['trade_name']}")
print(f"Status: {result['data']['status']}")
print(f"Type: {result['data']['business_type']}")
print(f"Registered: {result['data']['registration_date']}")
else:
print(f"Verification failed: {result.get('error')}")
```
What Are the Real-World Use Cases for GSTIN Verification?
GSTIN verification serves different purposes across business workflows. Each use case has specific requirements for verification depth, frequency, and response handling.
Use Case 1: Invoice validation. Before booking a purchase invoice for ITC claims, verify that the supplier's GSTIN is active and the legal name matches the invoice. Under Section 16(2) of CGST Act, ITC is only available on supplies from registered persons. Automate this check in your invoice processing pipeline to prevent ITC reversals and Section 73/74 notices. Use Case 2: Vendor onboarding. When adding a new supplier to your procurement system, verify their GSTIN as part of the onboarding checklist. Check that the business type (company, partnership, proprietorship) matches their claim, the principal place of business is in the claimed state, and the registration is active. Store the verification timestamp and result for audit trail. Use Case 3: E-invoicing pre-validation. Before generating an Invoice Registration Number (IRN) through the e-invoice system (mandated for businesses above ₹5 crore turnover per CBIC notification), validate both supplier and buyer GSTINs. An invalid GSTIN in the e-invoice JSON causes rejection by the IRP (Invoice Registration Portal), delaying the invoicing workflow. Use Case 4: GSTR-2A/2B reconciliation. Match GSTINs from your purchase register against those appearing in GSTR-2A (auto-populated from suppliers' GSTR-1). GSTIN verification helps resolve mismatches — a supplier might have registered a new GSTIN in a different state or their old GSTIN might have been cancelled. Use Case 5: KYC for financial services. NBFCs, fintech lenders, and insurance companies verify business GSTINs as part of entity KYC. The GST registration data — business type, registration date, filing compliance — serves as a proxy for business legitimacy and operational history.| Use Case | Verification Depth | Frequency | Critical Fields |
|
|
|
|
|
| Invoice validation | Status + legal name | Every invoice | Status, legal name, state |
| Vendor onboarding | Full details | Once + quarterly refresh | All fields, filing status |
| E-invoice pre-validation | Status only | Every e-invoice | Status, state code |
| GSTR-2A reconciliation | Status + trade name | Monthly | Status, trade/legal name |
| Financial KYC | Full details + filing | Onboarding + annual | All fields, filing history |
How Do You Handle GSTIN Verification Errors Gracefully?
Production GSTIN verification encounters multiple error types: format errors, API errors, and business-logic errors. Each requires different handling.
Format errors (caught before API call): Return immediate feedback to the user with the specific validation failure — invalid state code, bad PAN format, or check digit mismatch. For form inputs, validate on blur (as the user tabs away from the field) to provide instant feedback. API transport errors (network timeouts, rate limits, server errors): Implement retry with exponential backoff. For user-facing flows, show a "verification pending" state and retry in the background. For batch processing, queue failed verifications for retry in the next cycle. Never block a critical business workflow on a single API timeout — fall back to format-only validation with a flag for later re-verification. Business errors (GSTIN not found, cancelled, suspended): These are valid results, not errors. Handle each status appropriately:For teams building GST compliance workflows, the GSTIN lookup tool provides a quick browser-based verification interface, while the API integration described above handles programmatic verification at scale.
How Do You Build a GSTIN Verification Cache for Performance?
At scale, verifying GSTINs on every transaction is unnecessary and costly. A well-designed cache reduces API calls by 70-80% while maintaining data freshness.
1. Cache on first verification. When a GSTIN is successfully verified, store the full result with a timestamp. Use the GSTIN itself as the cache key.
2. Set TTL based on use case. For invoice validation, a 24-hour TTL is sufficient — a GSTIN is unlikely to change status within a day. For vendor onboarding, use fresh data (no cache). For batch reconciliation, 72-hour TTL balances freshness with cost.
3. Implement cache invalidation triggers. When a transaction involving a cached GSTIN fails (e.g., e-invoice rejection), immediately invalidate the cache entry and re-verify. When you detect a name mismatch between cache and a new invoice, re-verify immediately.
4. Use a two-tier cache. Keep a hot cache in memory (Redis) for sub-millisecond lookups during high-throughput processing. Back it with a database cache for persistence across restarts. The memory cache handles the 80% of lookups that hit frequently-used GSTINs (your top 100-200 suppliers), while the database cache covers the long tail.
5. Monitor cache hit rates. Track cache hit ratio and average verification latency. A healthy cache shows 70-80% hit rate. If the hit rate drops below 50%, your TTL might be too short or you are processing too many unique GSTINs (common during vendor onboarding drives).
What Compliance Requirements Apply to GSTIN Verification Data?
GSTIN verification data has both tax compliance and data protection implications. Understanding these requirements prevents regulatory issues.
Under GST audit provisions (Section 65, CGST Act), the tax department can request proof that you verified supplier GSTINs before claiming ITC. Maintain an audit log of every verification: GSTIN, verification timestamp, API response status, legal name returned, and the invoice or transaction it was verified for. Retain these logs for the minimum period prescribed under GST (72 months from the date of filing the annual return, per Section 36).
From a data protection perspective, GSTIN verification responses contain business addresses and sometimes individual names (for proprietorship GSTINs). Under the DPDP Act 2023, this constitutes personal data when linked to an identifiable individual. Process and store this data with purpose limitation — use it only for GST compliance purposes and do not repurpose it for marketing or profiling without proper consent.
For fintech applications processing GSTIN verification as part of lending or insurance KYC, maintain clear data lineage showing how GSTIN data flows through your system, who accesses it, and when it is deleted after the purpose is fulfilled.