🐍 Python🇮🇳 IndiaTax Compliance
Indian Business Compliance — GSTIN Validation
Validate India GST Identification Numbers (GSTIN) in Python using asyncio.gather for concurrent batch processing. Extract state codes, entity types, and cross-check embedded PAN numbers.
Also available in Node.js
Contents
1. GSTIN structure
A GSTIN is a 15-character alphanumeric identifier with a well-defined structure:
| Position | Characters | Content |
|---|---|---|
| 1–2 | 2 digits | State code (01–38, per Indian state/UT) |
| 3–12 | 10 chars | PAN number of the taxpayer |
| 13 | 1 digit | Entity number (1–9 for multiple registrations) |
| 14 | 1 char | Default "Z" |
| 15 | 1 char | Check character (alphanumeric) |
ℹ️Example:
27AAPFU0939F1ZV — state 27 (Maharashtra), PAN AAPFU0939F, entity 1.2. Validate a GSTIN
Use await iv.gstin(value) (SDK) or GET /v0/gstin?value=….
{ "valid": true, "gstin": "27AAPFU0939F1ZV", "stateCode": "27", "stateName": "Maharashtra", "pan": "AAPFU0939F", "entityNumber": "1", "entityType": "Private Limited Company", "checkChar": "V" }
- Check
valid— format + checksum - Use
state_namefor invoice state field - Store
panfor income tax cross-referencing - Log
entity_typefor compliance records
3. Batch validation with asyncio.gather
For vendor onboarding or invoice bulk processing, validate multiple GSTINs concurrently. Use return_exceptions=True so a single failure doesn't abort the whole batch.
import asyncio from isvalid_sdk import IsValidConfig, create_client config = IsValidConfig(api_key="YOUR_API_KEY") iv = create_client(config) async def validate_gstin(gstin: str): result = await iv.gstin(gstin) if not result.valid: raise ValueError(f"Invalid GSTIN: {gstin}") return result async def validate_batch(gstins: list[str]): results = await asyncio.gather( *[iv.gstin(g) for g in gstins], return_exceptions=True ) return dict(zip(gstins, results)) async def main(): # Single validation r = await validate_gstin("27AAPFU0939F1ZV") print(f"State: {r.state_name}, Entity: {r.entity_type}") print(f"PAN: {r.pan}") # Batch validation gstins = [ "27AAPFU0939F1ZV", # Maharashtra "09AAACH7409R1ZZ", # Uttar Pradesh "24AAACP8880D1Z5", # Gujarat ] batch = await validate_batch(gstins) for gstin, result in batch.items(): if isinstance(result, Exception): print(f"{gstin}: ERROR — {result}") else: print(f"{gstin}: valid={result.valid}, state={result.state_name}") asyncio.run(main())
4. Cross-checking the embedded PAN
A business may supply its PAN separately during onboarding. Use the embedded PAN from GSTIN to verify consistency:
async def verify_gstin_pan_match(gstin: str, declared_pan: str) -> bool: result = await iv.gstin(gstin) if not result.valid: return False return result.pan == declared_pan.upper() # Usage match = await verify_gstin_pan_match("27AAPFU0939F1ZV", "AAPFU0939F") if not match: raise ValueError("GSTIN does not match the declared PAN")
⚠️If a customer provides multiple GSTINs for different states, all of them should share the same embedded PAN. A mismatch across GSTINs may indicate data entry errors or fraudulent documents.
5. Edge cases
State code mismatch on invoices
⚠️For inter-state B2B transactions, supplier and buyer state codes must differ (IGST applies). For intra-state, they must match (CGST + SGST apply). Extract state codes from both GSTINs.
supplier, buyer = await asyncio.gather( iv.gstin(supplier_gstin), iv.gstin(buyer_gstin), ) if supplier.state_code == buyer.state_code: tax_type = "CGST + SGST" # intra-state else: tax_type = "IGST" # inter-state
Format valid but registration cancelled
ℹ️The API validates GSTIN format and structure. It does not query the live GST portal for active registration status. For high-value transactions, additionally verify on the official GST portal.
Multiple registrations — same PAN
# Entity number (pos 13) distinguishes multiple state registrations # for the same business # "27AAPFU0939F1ZV" and "29AAPFU0939F2ZR" = same PAN, different states gstins = ["27AAPFU0939F1ZV", "29AAPFU0939F2ZR"] results = await asyncio.gather(*[iv.gstin(g) for g in gstins]) pans = {r.pan for r in results if r.valid} if len(pans) == 1: print("All GSTINs belong to the same business")
6. Summary checklist
✓Validate GSTIN format + checksum via API
✓Extract state code for IGST/CGST determination
✓Cross-check embedded PAN with declared PAN
✓Use asyncio.gather for batch onboarding
✓Handle return_exceptions=True in batch calls
✓Verify all GSTINs share same PAN for one business
✓Note: API validates structure, not active registration
✓Return 422 with field-level error messages