How to Integrate a GST Verification API in PHP
Step-by-step PHP code to call a GST verification API with cURL and Guzzle, and handle every GSTIN validation response.
You can integrate a GST verification API in PHP with nothing more than the built-in cURL extension, or with Guzzle if your project already depends on it — both call the same endpoint and return the same JSON. This guide shows the raw cURL version first, then a Guzzle client with retries, then a PSR-18 abstraction so your application code is not locked to either library.
Validate the GSTIN before you call the API
A GSTIN is 15 characters, and the last one is a check digit computed from the first 14 using a Luhn mod-36 style algorithm. Validating this locally in PHP — before any HTTP call — rejects obviously malformed input for free. It does not prove the GSTIN is real; it only proves the string is well-formed, which is exactly why the live lookup later in this post still matters.
<?php
function gstinChecksumIsValid(string $gstin): bool
{
$charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$gstin = strtoupper($gstin);
if (strlen($gstin) !== 15) {
return false;
}
$body = substr($gstin, 0, 14);
$checkChar = $gstin[14];
$sum = 0;
for ($i = 0; $i < 14; $i++) {
$pos = strpos($charset, $body[$i]);
if ($pos === false) {
return false;
}
$factor = ($i % 2 === 0) ? 1 : 2;
$product = $pos * $factor;
$sum += intdiv($product, 36) + ($product % 36);
}
$expected = $charset[(36 - ($sum % 36)) % 36];
return $expected === $checkChar;
}
A raw cURL request
For a script, a cron job, or any codebase that should not carry an extra dependency for one API call, raw
cURL against GET https://gstinapi.com/api/get-taxpayer-info/{gstin} is enough. Send the API key
as the X-API-Key header, exactly as documented.
<?php
function getTaxpayerInfoCurl(string $gstin, string $apiKey): array
{
if (! gstinChecksumIsValid($gstin)) {
throw new InvalidArgumentException("'{$gstin}' is not a structurally valid GSTIN");
}
$ch = curl_init("https://gstinapi.com/api/get-taxpayer-info/{$gstin}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: {$apiKey}"],
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($body === false) {
throw new RuntimeException("cURL request failed: {$error}");
}
if ($status < 200 || $status >= 300) {
throw new RuntimeException("GST verification API returned HTTP {$status}: {$body}");
}
return json_decode($body, true, flags: JSON_THROW_ON_ERROR);
}
A Guzzle client with retries
Guzzle is the better fit once this call is one of several outbound integrations in a real application. Its handler stack lets you add retry middleware declaratively, instead of writing your own retry loop around cURL, and it gives connection pooling for free when you reuse a single client instance.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;
final class GstinClient
{
private Client $http;
public function __construct(private string $apiKey)
{
$stack = HandlerStack::create();
$stack->push(Middleware::retry(
function (int $retries, RequestInterface $request, ?ResponseInterface $response) {
if ($retries >= 3) {
return false;
}
return $response && in_array($response->getStatusCode(), [429, 500, 502, 503, 504], true);
},
fn (int $retries) => 500 * (2 ** $retries)
));
$this->http = new Client([
'base_uri' => 'https://gstinapi.com/api/',
'headers' => ['X-API-Key' => $this->apiKey],
'timeout' => 10,
'handler' => $stack,
]);
}
public function getTaxpayerInfo(string $gstin): array
{
if (! gstinChecksumIsValid($gstin)) {
throw new InvalidArgumentException("'{$gstin}' is not a structurally valid GSTIN");
}
$response = $this->http->get("get-taxpayer-info/{$gstin}");
return json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
}
}
Reliance Industries’ public GSTIN, 27AAACR5055K1Z7, is a good one to try this against manually —
its real response returns 354 filing records and 244 additional places of business, so it is a solid test of
how your code handles a large payload before you point it at smaller businesses.
Abstracting behind PSR-18
If you want your GSTIN lookup code to be independent of Guzzle specifically, code it against
Psr\Http\Client\ClientInterface and PSR-17 request/response factories instead of Guzzle’s
concrete Client. Guzzle implements PSR-18 out of the box, so this costs almost nothing today and
leaves you free to swap in Symfony’s HttpClient or another PSR-18 implementation later without rewriting the
verification logic itself.
<?php
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
final class Psr18GstinClient
{
public function __construct(
private ClientInterface $client,
private RequestFactoryInterface $requestFactory,
private string $apiKey,
) {}
public function getTaxpayerInfo(string $gstin): array
{
if (! gstinChecksumIsValid($gstin)) {
throw new InvalidArgumentException("'{$gstin}' is not a structurally valid GSTIN");
}
$request = $this->requestFactory
->createRequest('GET', "https://gstinapi.com/api/get-taxpayer-info/{$gstin}")
->withHeader('X-API-Key', $this->apiKey);
$response = $this->client->sendRequest($request);
if ($response->getStatusCode() >= 400) {
throw new RuntimeException(
"GST verification API returned HTTP {$response->getStatusCode()}"
);
}
return json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
}
}
Optionally, a plain DTO instead of a raw array
All three clients above hand back a decoded associative array, which is fine for a small script but starts to hurt once several parts of an application read from it — a typo in an array key fails silently at runtime instead of at compile time. If the codebase is large enough to care, map the array onto a plain readonly class before it leaves the client, so the rest of the application works against named properties instead of string keys.
<?php
final class TaxpayerInfo
{
public function __construct(
public readonly string $gstin,
public readonly string $legalName,
public readonly ?string $tradeName,
public readonly string $status,
public readonly string $registrationDate,
public readonly ?string $cancellationDate,
public readonly string $constitution,
public readonly string $taxpayerType,
) {}
public static function fromArray(array $data): self
{
return new self(
gstin: $data['gstin'],
legalName: $data['legal_name'],
tradeName: $data['trade_name'] ?? null,
status: $data['status'],
registrationDate: $data['registration_date'],
cancellationDate: $data['cancellation_date'] ?? null,
constitution: $data['constitution'],
taxpayerType: $data['taxpayer_type'],
);
}
}
This is optional — a straightforward script has no real need for it — but it pays off quickly once the same response is consumed by more than one part of the codebase, since PHP’s readonly properties give you a compile-time-checked shape instead of an untyped array passed around by convention.
Plain PHP versus a Laravel app
Everything above assumes a plain PHP application — a script, a Slim or Symfony app, or anything without a framework-provided HTTP client. If your codebase is Laravel, do not port this code as-is: Laravel’s HTTP client facade already wraps Guzzle with a fluent, testable API, and Form Requests are the idiomatic place to validate the GSTIN parameter before your controller ever calls the API. Rather than duplicate that guidance here, see how to integrate the GST verification API in Laravel for the Laravel-specific version of this same integration.
Handling errors and response shape
All three clients above return a decoded array (or you can map it to a DTO if you prefer typed objects). The response includes legal name, trade name, current status, registration and cancellation dates, constitution, taxpayer type, centre and state jurisdiction, e-invoicing status, principal and additional places of business, and the full GSTR filing history with per-return ARN and timeliness — see the full schema on the features page. Handle a 401 as an invalid or missing API key, a 404 as a GSTIN not found in the underlying registry, and a 429 as a rate limit that your retry logic should back off on, not retry immediately.
| Approach | Best for |
|---|---|
| Raw cURL | One-off scripts, cron jobs, zero-dependency environments |
| Guzzle | Applications that already depend on Guzzle or want built-in retry middleware |
| PSR-18 abstraction | Libraries or codebases that must not be locked to one HTTP client |
| Laravel HTTP client | Laravel applications — see the dedicated Laravel guide |
For high-volume verification — checking an entire vendor master rather than one GSTIN at a time — see the bulk GSTIN verification guide, and for the meaning of individual error responses, the API error codes guide.
Caching and rate-limit-friendly usage
Store the API key in an environment variable — through getenv(), a .env file, or
your framework’s configuration layer — never hardcoded in a committed file. Set explicit connect and
request timeouts on every client above, since none of PHP’s HTTP layers time out by default in a way you
should rely on.
Because registration status, filing history, and e-invoicing eligibility do not change from one minute to the next, it is usually safe to cache a taxpayer lookup for a few hours using APCu, Redis, or your framework’s cache store, keyed on the GSTIN. For a business that checks the same handful of vendor GSTINs repeatedly — on every invoice, say — this alone can cut real credit spend substantially without any loss of accuracy that matters in practice.
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.