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") == falseThis 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
| Code | Meaning | What to do |
|---|---|---|
| 400 | Invalid GSTIN format | No credit charged. Validate client-side and this never fires. |
| 401 | Missing or invalid x-api-key | Check the header name and that the key is not truncated. |
| 402 | Out of credits | Different from 404 — the lookup never ran. Surface a recharge prompt, not "not found". |
| 404 | GSTIN not registered | A valid-format GSTIN that the GST network has no record of. |
| 429 | Rate limit exceeded | 60 requests/minute on standard accounts. Retry with backoff. |
| 502 | GST provider unavailable | Upstream 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.