Go's standard library handles HTTP calls without any third-party dependencies, making the TaxID API integration straightforward. The full implementation — struct types, HTTP client, context timeout, all error states, and a thread-safe in-memory cache — fits in a single file. For the use-case guide with step-by-step wiring, see the Go VAT validation use case.
Response Types
package vat
type Status string
const (
StatusActive Status = "active"
StatusInactive Status = "inactive"
StatusFormatInvalid Status = "format_invalid"
StatusServiceUnavail Status = "service_unavailable"
)
type Response struct {
Valid bool `json:"valid"`
Status Status `json:"status"`
Vat string `json:"vat"`
CountryCode string `json:"country_code"`
CompanyName string `json:"company_name"`
Address string `json:"address"`
Cached bool `json:"cached"`
RequestID string `json:"request_id"`
}HTTP Client
package vat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
const baseURL = "https://taxid.dev/api/v1"
var httpClient = &http.Client{Timeout: 6 * time.Second}
func Validate(ctx context.Context, vatNumber string) (*Response, error) {
normalised := strings.ToUpper(strings.ReplaceAll(vatNumber, " ", ""))
country := normalised[:2]
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("%s/validate/%s/%s", baseURL, country, normalised),
nil,
)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TAXID_API_KEY"))
resp, err := httpClient.Do(req)
if err != nil {
return &Response{Status: StatusServiceUnavail}, nil // network error → unavailable
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("taxid api: %d", resp.StatusCode)
}
var result Response
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}Thread-Safe Cache with sync.Map
package vat
import (
"context"
"sync"
"time"
)
type cachedResult struct {
result *Response
cachedAt time.Time
}
type Client struct {
cache sync.Map
maxAge time.Duration
}
func NewClient(cacheMaxAge time.Duration) *Client {
return &Client{maxAge: cacheMaxAge}
}
func (c *Client) Validate(ctx context.Context, vatNumber string) (*Response, error) {
key := strings.ToUpper(strings.ReplaceAll(vatNumber, " ", ""))
// Check cache
if v, ok := c.cache.Load(key); ok {
entry := v.(cachedResult)
if time.Since(entry.cachedAt) < c.maxAge {
r := *entry.result
r.Cached = true
return &r, nil
}
}
result, err := Validate(ctx, vatNumber)
if err != nil {
return nil, err
}
// Only cache definitive results
if result.Status != StatusServiceUnavail {
c.cache.Store(key, cachedResult{result: result, cachedAt: time.Now()})
}
return result, nil
}
// Usage:
// client := vat.NewClient(23 * time.Hour)
// result, err := client.Validate(ctx, "DE123456789")HTTP Handler (net/http)
package handlers
import (
"encoding/json"
"net/http"
"your/module/vat"
)
var vatClient = vat.NewClient(23 * time.Hour)
func ValidateVatHandler(w http.ResponseWriter, r *http.Request) {
vatNumber := r.URL.Query().Get("vat")
if vatNumber == "" {
http.Error(w, `{"error":"vat_required"}`, http.StatusBadRequest)
return
}
result, err := vatClient.Validate(r.Context(), vatNumber)
if err != nil {
http.Error(w, `{"error":"api_error"}`, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"valid": result.Valid,
"status": result.Status,
"companyName": result.CompanyName,
})
}Related guides
Start validating EU VAT numbers
Free plan — 100 validations/month. No credit card required.