How to verify a GST number in Node.js

Verifying an Indian GSTIN from Node.js, end to end: validating the format offline so a typo never costs a credit, making the call, and handling every status the API can return. Works with plain Node, Express, or Next.js route handlers.

Up to 100 free lookups — they never expire. No credit card.

1. Install the Node.js library

An official client library exists for Node.js, and it handles the auth header and response parsing for you.

npm install gstinapi

Then verify a GSTIN in three lines:

import { GstinApi } from 'gstinapi';

const client = new GstinApi({ apiKey: 'gak_your_key_here' });

const result = await client.verify('33AAACC1206D1ZN');

console.log(result.legal_name);    // "CHENNAI CORPORATION LIMITED"
console.log(result.status);        // "Active"
console.log(result.taxpayer_type); // "Regular"

Source on GitHub · package registry

Without the package (native fetch)

The API is a single GET with one header, so you can skip the library entirely if you would rather not add a dependency.

const response = await fetch(
  'https://gstinapi.in/v1/gstin/33AAACC1206D1ZN',
  { headers: { 'x-api-key': 'gak_your_key_here' } }
);

if (!response.ok) throw new Error(`HTTP ${response.status}`);

const data = await response.json();
console.log(data.legal_name, data.status);

2. Validate the GSTIN offline first

A malformed GSTIN returns 400 and is never charged — but you still paid for the round trip. The check digit is computable locally, so reject typos before they leave your server.

const CODES = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

export function isValidGstin(gstin) {
  const value = (gstin ?? '').trim().toUpperCase();
  if (value.length !== 15) return false;

  let total = 0;
  for (let i = 0; i < 14; i++) {
    const index = CODES.indexOf(value[i]);
    if (index === -1) return false;
    const product = index * (i % 2 ? 2 : 1);
    total += Math.floor(product / 36) + (product % 36);
  }

  const expected = CODES[(36 - (total % 36)) % 36];
  return value[14] === expected;
}

isValidGstin('33AAACC1206D1ZN'); // true
isValidGstin('33AAACC1206D1ZZ'); // false

This proves the GSTIN is well-formed. It cannot tell you whether the registration exists or is still active — only a live lookup does that, because registrations get cancelled.

3. Handle every status code

CodeMeaningWhat to do
400Invalid GSTIN formatNo credit charged. Validate client-side and this never fires.
401Missing or invalid x-api-keyCheck the header name and that the key is not truncated.
402Out of creditsDifferent from 404 — the lookup never ran. Surface a recharge prompt, not "not found".
404GSTIN not registeredA valid-format GSTIN that the GST network has no record of.
429Rate limit exceeded60 requests/minute on standard accounts. Retry with backoff.
502GST provider unavailableUpstream GSP hiccup. Safe to retry — nothing was charged.

Retry policy. Retry only on 429 and 502, with exponential backoff, maximum 3 attempts. Never retry 400, 401, 402, 403 or 404 — the answer will not change.

const RETRYABLE = new Set([429, 502]);
const sleep = ms => new Promise(r => setTimeout(r, ms));

export async function verify(gstin, apiKey, attempts = 3) {
  for (let attempt = 0; attempt < attempts; attempt++) {
    const res = await fetch(`https://gstinapi.in/v1/gstin/${gstin}`, {
      headers: { 'x-api-key': apiKey },
    });

    if (res.ok) return res.json();
    if (res.status === 404) return null;   // valid format, no such registration
    if (res.status === 402) throw new Error('Out of credits — recharge your account');

    if (!RETRYABLE.has(res.status)) {
      const { error } = await res.json().catch(() => ({}));
      throw new Error(error ?? `HTTP ${res.status}`);
    }

    if (attempt < attempts - 1) await sleep(2 ** attempt * 1000);
  }

  throw new Error('GST provider unavailable after retries');
}

4. Wiring it into Express or Next.js

Read the key from process.env and call the API server-side only — a key shipped to the browser is a key anyone can spend. In Next.js that means a route handler or server action, never a client component. Cache the result against the vendor record rather than re-verifying on every request.

// app/api/verify/route.js
import { GstinApi } from 'gstinapi';

const client = new GstinApi({ apiKey: process.env.GSTIN_API_KEY });

export async function POST(request) {
  const { gstin } = await request.json();
  const result = await client.verify(gstin);
  return Response.json(result);
}

Frequently asked questions

Is there a free GST verification API for Node.js?

Yes. Every account gets up to 100 free lookups, and unlike most trials the credits never expire — no clock running against you. No credit card is required to create a key.

Can I call the GST API from the browser?

No — not with your API key. Anything shipped to the browser is readable by the user, and a leaked key spends your credits. Call it from a server route and pass only the result to the frontend. If you want a public-facing lookup with no key, the free search page at /gst-number-search is rate-limited by IP for exactly that reason.

Does the npm package work with TypeScript?

The package is published with the client shown above. If you need strict typing over the raw response, the full field list and every status code are documented on the API reference page.

How do I handle rate limits in Node?

Standard accounts allow 60 requests per minute. When you exceed it you get a 429, which is transient — back off exponentially and retry, capped at about 3 attempts. If you are processing a large vendor list concurrently, cap concurrency well below 60/min rather than firing everything at once.

The same guide in another language

Ready to integrate?

Create an account, generate a key, and you start with 25 free lookups — up to 100 once you finish the setup steps. No card, and they never expire.

Need to check a single GSTIN right now? Use the free search tool — no signup.