How to verify a GST number in Go

There is no Go SDK — the API is one GET with one header, so net/http and encoding/json from the standard library cover it with no dependencies. This shows the call, a typed struct for the response, offline check-digit validation, and the retry policy that matters in production.

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

1. The call, with net/http

There is no Go SDK, and you do not need one — the API is a single GET request with one header.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type GstinResult struct {
	Gstin        string `json:"gstin"`
	LegalName    string `json:"legal_name"`
	TradeName    string `json:"trade_name"`
	Status       string `json:"status"`
	TaxpayerType string `json:"taxpayer_type"`
	Address      string `json:"address"`
}

func main() {
	client := &http.Client{Timeout: 10 * time.Second}

	req, _ := http.NewRequest("GET", "https://gstinapi.in/v1/gstin/33AAACC1206D1ZN", nil)
	req.Header.Set("x-api-key", "gak_your_key_here")

	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var result GstinResult
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		panic(err)
	}
	fmt.Println(result.LegalName, result.Status)
}

2. Validate the GSTIN offline first

A malformed GSTIN returns 400 and costs no credit, but the round trip is still wasted. Validate the check digit locally and typos never reach the network.

const codes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

// IsValidGstin checks the format and check digit without calling the API.
func IsValidGstin(gstin string) bool {
	value := strings.ToUpper(strings.TrimSpace(gstin))
	if len(value) != 15 {
		return false
	}

	total := 0
	for i := 0; i < 14; i++ {
		index := strings.IndexByte(codes, value[i])
		if index < 0 {
			return false
		}
		product := index
		if i%2 == 1 {
			product = index * 2
		}
		total += product/36 + product%36
	}

	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.

var ErrOutOfCredits = errors.New("out of credits")

// Verify returns nil, nil when the GSTIN is not registered.
func Verify(client *http.Client, gstin, apiKey string) (*GstinResult, error) {
	const attempts = 3

	for attempt := 0; attempt < attempts; attempt++ {
		req, err := http.NewRequest("GET", "https://gstinapi.in/v1/gstin/"+gstin, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("x-api-key", apiKey)

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}

		switch resp.StatusCode {
		case http.StatusOK:
			var result GstinResult
			err := json.NewDecoder(resp.Body).Decode(&result)
			resp.Body.Close()
			return &result, err
		case http.StatusNotFound:
			resp.Body.Close()
			return nil, nil // not registered
		case http.StatusPaymentRequired:
			resp.Body.Close()
			return nil, ErrOutOfCredits
		case http.StatusTooManyRequests, http.StatusBadGateway:
			resp.Body.Close()
			if attempt < attempts-1 {
				time.Sleep(time.Second << attempt) // 1s, then 2s
			}
		default:
			resp.Body.Close()
			return nil, fmt.Errorf("lookup failed with HTTP %d", resp.StatusCode)
		}
	}

	return nil, errors.New("GST provider unavailable after retries")
}

4. Sharing one client across your service

Create one http.Client when the service starts and reuse it for every lookup. It is safe for concurrent use and keeps connections alive between calls, while a new client per request opens a new connection each time. Read the key from the environment, never from source.

type GstinService struct {
	client *http.Client
	apiKey string
}

func NewGstinService() *GstinService {
	return &GstinService{
		client: &http.Client{Timeout: 10 * time.Second},
		apiKey: os.Getenv("GSTIN_API_KEY"),
	}
}

func (s *GstinService) Verify(gstin string) (*GstinResult, error) {
	if !IsValidGstin(gstin) {
		return nil, errors.New("invalid GSTIN")
	}
	return Verify(s.client, gstin, s.apiKey)
}

Frequently asked questions

Is there a Go package for GST verification?

Not an official one. The API is a single GET with one header, so net/http and encoding/json from the standard library are the entire integration. Official libraries exist for Node.js and Python.

Do I need any third-party dependency?

No. Everything on this page uses the Go standard library only.

How do I verify GST numbers in bulk from Go?

Iterate your list against the same endpoint, keeping total throughput under the 60 requests/minute limit. If you use goroutines, bound them with a buffered channel or a rate limiter rather than starting one per GSTIN, and treat 429 as a signal to back off.

Can I validate a GSTIN in Go without calling an API?

You can validate the format and the check digit offline with the function on this page. That catches typos for free. It cannot tell you whether the GSTIN is registered or still active, which needs a live lookup.

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.