How to Integrate a GST Verification API in Python
A hands-on Python tutorial for validating GSTINs in real time using requests, with retries, typing and a reusable client.
To integrate a GST verification API in Python, build a small typed client around requests.Session,
attach a urllib3 retry adapter for transient failures, and model the JSON response with
dataclasses so the rest of your codebase never touches raw dictionaries. This post walks through
that client end to end, including offline GSTIN validation and a pytest suite that mocks the HTTP layer with
the responses library.
What a Python integration actually needs
A GST verification API integration in Python is a small piece of
software, but it has the same requirements
as any other outbound HTTP client: correct headers, sane timeouts, retry logic that does not make failures
worse, and a response shape your application code can rely on. The endpoint itself is simple — a single
GET request per GSTIN — so most of the engineering effort goes into making the client
predictable to call and easy to test, not into the HTTP call itself.
The API you are calling is GET https://gstinapi.com/api/get-taxpayer-info/{gstin}, authenticated
with an X-API-Key header. Every account gets 20 free verification credits on signup, and each
successful lookup consumes one credit — which is exactly why client-side validation, covered next, matters
more in Python than it might in a language with weaker typing discipline.
Validate the GSTIN before you spend a credit
A GSTIN is a 15-character string with a fixed structure, and the 15th character is a check digit computed from the first 14 using a Luhn mod-36 style algorithm. Validating both the shape and the check digit locally, before the API call, catches typos and obviously malformed input without touching the network — which means it does not cost a verification credit. Note that a valid checksum only proves the string is well-formed; it does not prove the GSTIN was ever issued, which is exactly what the live API call in the next section resolves.
GSTIN_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def gstin_checksum_is_valid(gstin: str) -> bool:
"""Validate a GSTIN's structure and Luhn mod-36 check digit locally."""
if len(gstin) != 15:
return False
if any(ch not in GSTIN_CHARSET for ch in gstin.upper()):
return False
body, check_char = gstin[:14].upper(), gstin[14].upper()
total = 0
for i, ch in enumerate(body):
value = GSTIN_CHARSET.index(ch)
factor = 1 if i % 2 == 0 else 2
product = value * factor
total += product // 36 + product % 36
expected = GSTIN_CHARSET[(36 - (total % 36)) % 36]
return expected == check_char
A typed client with a session and retries
Wrap the API in a small class rather than calling requests.get inline wherever you need a
lookup. A requests.Session reuses the underlying TCP connection across calls, and mounting a
urllib3 Retry adapter on it gives you automatic, bounded retries for the failure
modes that are actually worth retrying — connection resets, timeouts, and 429/5xx responses — while leaving
4xx errors, like a bad API key or a rejected GSTIN, to fail immediately.
from __future__ import annotations
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class GstinApiError(Exception):
"""Raised for non-2xx responses from the GST verification API."""
def __init__(self, status_code: int, message: str):
super().__init__(message)
self.status_code = status_code
class GstinClient:
BASE_URL = "https://gstinapi.com/api"
def __init__(self, api_key: str, timeout: float = 10.0):
self.api_key = api_key
self.timeout = timeout
self.session = requests.Session()
retry = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"],
)
adapter = HTTPAdapter(max_retries=retry)
self.session.mount("https://", adapter)
self.session.headers.update({"X-API-Key": self.api_key})
def get_taxpayer_info(self, gstin: str):
if not gstin_checksum_is_valid(gstin):
raise ValueError(f"'{gstin}' is not a structurally valid GSTIN")
response = self.session.get(
f"{self.BASE_URL}/get-taxpayer-info/{gstin}",
timeout=self.timeout,
)
if not response.ok:
raise GstinApiError(response.status_code, response.text)
return TaxpayerInfo.from_dict(response.json())
Modelling the response with dataclasses
The live API returns a rich payload — legal name, trade name, status, registration and cancellation dates,
constitution, taxpayer type, jurisdiction, nature of business activities, e-invoicing status, principal and
additional places of business, and the full GSTR filing history with per-return ARN and timeliness. A
dataclass with a small from_dict constructor keeps that structure explicit instead of passing
a raw dictionary around your codebase, where a typo in a key name fails silently.
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class TaxpayerInfo:
gstin: str
legal_name: str
trade_name: Optional[str]
status: str
registration_date: str
cancellation_date: Optional[str]
constitution: str
taxpayer_type: str
@classmethod
def from_dict(cls, data: dict) -> "TaxpayerInfo":
return cls(
gstin=data["gstin"],
legal_name=data["legal_name"],
trade_name=data.get("trade_name"),
status=data["status"],
registration_date=data["registration_date"],
cancellation_date=data.get("cancellation_date"),
constitution=data["constitution"],
taxpayer_type=data["taxpayer_type"],
)
Trim or extend this dataclass to match the fields your product actually consumes — a lending workflow might
only need status and constitution, while an accounts-payable tool will want the full
filing history to flag a vendor who is behind on GSTR-3B. See the full response schema in the
API documentation.
Calling the client
With the client and dataclass in place, a lookup is a couple of lines anywhere in your application. Reliance
Industries’ public GSTIN, 27AAACR5055K1Z7, is a useful one to test against manually — its real
response includes 354 filing records and 244 additional places of business, which is a good stress test for
any UI you build on top of this data.
client = GstinClient(api_key="YOUR_API_KEY")
info = client.get_taxpayer_info("27AAACR5055K1Z7")
print(info.legal_name, info.status)
Testing with pytest and responses
Do not hit the live API in unit tests — it costs a credit per call and makes your test suite dependent on
network access. The responses library patches requests at the transport layer, so
you can register an exact URL and payload and assert your client parses it correctly.
import responses
from gstin_client import GstinClient
@responses.activate
def test_get_taxpayer_info_parses_response():
responses.add(
responses.GET,
"https://gstinapi.com/api/get-taxpayer-info/27AAACR5055K1Z7",
json={
"gstin": "27AAACR5055K1Z7",
"legal_name": "RELIANCE INDUSTRIES LIMITED",
"trade_name": "RELIANCE INDUSTRIES LIMITED",
"status": "Active",
"registration_date": "2017-07-01",
"cancellation_date": None,
"constitution": "Public Limited Company",
"taxpayer_type": "Regular",
},
status=200,
)
client = GstinClient(api_key="test-key")
info = client.get_taxpayer_info("27AAACR5055K1Z7")
assert info.status == "Active"
assert info.legal_name == "RELIANCE INDUSTRIES LIMITED"
@responses.activate
def test_rejects_malformed_gstin_without_calling_api():
client = GstinClient(api_key="test-key")
try:
client.get_taxpayer_info("NOT-A-GSTIN")
assert False, "expected ValueError"
except ValueError:
pass
assert len(responses.calls) == 0
Handling errors and rate limits
The GstinApiError in the client above carries the HTTP status code, so callers can branch on it:
a 401 means the API key is missing or wrong, a 404 typically means the GSTIN is not found in the underlying
registry, and a 429 means you are rate limited — which the retry adapter already backs off on automatically.
Log the status code and a truncated response body on failure, not the full payload, since GST filing data can
be sensitive.
For high-volume use, batch and pace your calls rather than firing hundreds of concurrent requests — see the bulk GSTIN verification guide and rate limits and webhooks post for patterns that apply regardless of language. If you are building this inside a Django or Flask app rather than a script, the same client class drops in as a service object with no changes.
Once your Python client is solid, the same request/response shape applies whether you are calling it from a Laravel app in PHP, a Java service, or a .NET backend — see our guides for PHP, Java, and .NET/C# if your stack spans more than one language. For the full field list this API returns, check the features page, and see pricing for credit packs beyond the free 20.
Logging, secrets, and timeouts
Keep the API key out of source control — load it from an environment variable or a secrets manager, not a
hardcoded string in the client. Set an explicit timeout on every call rather than relying on
the default, since requests will otherwise wait indefinitely on a hanging connection. When you
log a failed lookup, log the HTTP status code and the GSTIN, but avoid logging the full response body —
filing history and address data returned by the API can be sensitive, and most teams treat it the same way
they treat other PII in application logs.
One more thing worth building in early: a thin caching layer in front of the client for GSTINs you look up repeatedly, such as a vendor whose status you check on every invoice. Registration status, filing history, and e-invoicing eligibility do not change minute to minute, so caching a lookup for a few hours is usually safe and cuts your credit spend meaningfully on a busy integration.
Frequently asked questions
A note on accuracy. GST rules change often. This article reflects our understanding as of 30 August 2026 and is general information, not tax or legal advice. For the authoritative position, check gst.gov.in and cbic-gst.gov.in, or speak to a qualified tax professional about your specific situation.
Check a GSTIN right now, free
Five lookups a day, no account required. Status, filing history, e-invoicing and jurisdiction.
Free GST search toolBuild it into your product
One REST call, JSON back. Get an API key and 20 free verification credits in under a minute.
Start freeMore on developer integration guides
Browse every guide in the GST API blog.