Official Go SDK for the ZipTax API - get accurate sales and use tax rates for any US address or geolocation.
- 🚀 Simple, idiomatic Go API
- 🔄 Automatic retry with exponential backoff
- ⏱️ Context support for timeouts and cancellation
- 🔍 Input validation
- 🧪 Comprehensive test coverage (>88%)
- 📝 Full type safety with Go structs
- 🔐 Secure API key authentication
- 🌐 Support for US and Canadian addresses
- 📍 Geolocation-based lookups
- 🏷️ Product Code (TIC) Search - Search and AI-powered recommendation for Taxability Information Codes
- 🛒 Cart Tax Calculation - Calculate sales tax on shopping carts (ZipTax or TaxCloud)
- 📦 TaxCloud Order Management - Create, retrieve, update, and refund orders
go get github.com/ziptax/ziptax-gopackage main
import (
"context"
"fmt"
"log"
"github.com/ziptax/ziptax-go"
)
func main() {
// Create a new client with your API key
client, err := ziptax.NewClient("your-api-key-here")
if err != nil {
log.Fatal(err)
}
// Get sales tax by address
ctx := context.Background()
response, err := client.GetSalesTaxByAddress(ctx, "200 Spectrum Center Drive, Irvine, CA 92618")
if err != nil {
log.Fatal(err)
}
// Print the results
fmt.Printf("Address: %s\n", response.AddressDetail.NormalizedAddress)
if len(response.TaxSummaries) > 0 {
fmt.Printf("Total Tax Rate: %.4f%%\n", response.TaxSummaries[0].Rate*100)
}
}Create a client with default settings (ZipTax API only):
client, err := ziptax.NewClient("your-api-key")Or customize the client with options:
client, err := ziptax.NewClient(
"your-api-key",
ziptax.WithTimeout(60*time.Second),
ziptax.WithMaxRetries(5),
ziptax.WithRetryWait(2*time.Second, 60*time.Second),
)Enable TaxCloud Order Management (Optional):
To use TaxCloud order features, provide both TaxCloud credentials during initialization:
client, err := ziptax.NewClient(
"your-ziptax-api-key",
ziptax.WithTaxCloudConnectionID("your-taxcloud-connection-id"),
ziptax.WithTaxCloudAPIKey("your-taxcloud-api-key"),
)response, err := client.GetSalesTaxByAddress(
ctx,
"200 Spectrum Center Drive, Irvine, CA 92618",
)With optional parameters:
response, err := client.GetSalesTaxByAddress(
ctx,
"200 Spectrum Center Drive, Irvine, CA 92618",
ziptax.WithHistorical("202401"),
ziptax.WithCountryCode("USA"),
ziptax.WithFormat("json"),
)response, err := client.GetSalesTaxByGeoLocation(
ctx,
"33.65253", // latitude
"-117.74794", // longitude
)response, err := client.GetRatesByPostalCode(ctx, "92694")
for _, result := range response.Results {
fmt.Printf("City: %s, Total Tax Rate: %.4f%%\n", result.GeoCity, result.TaxSales*100)
}metrics, err := client.GetAccountMetrics(ctx)
fmt.Printf("Core Usage: %.2f%%\n", metrics.CoreUsagePercent)
fmt.Printf("Geo Usage: %.2f%%\n", metrics.GeoUsagePercent)Search for Taxability Information Codes (TICs) using natural language product descriptions. No TaxCloud credentials required -- these endpoints use the ZipTax API.
Returns all matching TICs ranked and scored by relevance:
response, err := client.SearchProductCodes(ctx, "baked goods sold in plastic packaging")
if err != nil {
log.Fatal(err)
}
for _, result := range response.Results {
fmt.Printf("TIC %s: %s (rank %s, score %s)\n",
result.TicID, result.Label, result.Rank, result.Score)
}Get an AI-powered best-match recommendation (slightly higher latency):
response, err := client.RecommendProductCode(ctx, "baked goods sold in plastic packaging")
if err != nil {
log.Fatal(err)
}
prediction := response.Predictions[0]
if prediction.Status == "success" {
fmt.Printf("Recommended TIC: %s (%s)\n", prediction.TicID, prediction.Label)
fmt.Printf("Description: %s\n", prediction.TicDescription)
}Use the returned TicID as the TaxabilityCode in cart line items:
import (
"log"
"strconv"
)
ticID := response.Results[0].TicID
tic, err := strconv.ParseInt(ticID, 10, 64)
if err != nil {
log.Fatalf("failed to parse TIC ID %q: %v", ticID, err)
}
lineItem := models.CartLineItem{
ItemID: "item-1",
Price: 10.00,
Quantity: 1,
TaxabilityCode: &tic,
}Calculate sales tax on a shopping cart. The SDK routes the request automatically based on your client configuration:
- Without TaxCloud credentials: Routes to the ZipTax
/calculate/cartAPI - With TaxCloud credentials: Routes to the TaxCloud
/tax/connections/{connectionId}/cartsAPI
cartReq := &models.CalculateCartRequest{
Items: []models.CartItem{
{
CustomerID: "customer-453",
Currency: models.CartCurrency{CurrencyCode: "USD"},
Destination: models.CartAddress{
Address: "200 Spectrum Center Dr, Irvine, CA 92618",
},
Origin: models.CartAddress{
Address: "323 Washington Ave N, Minneapolis, MN 55401-2427",
},
LineItems: []models.CartLineItem{
{
ItemID: "item-1",
Price: 10.75,
Quantity: 1.5,
},
},
},
},
}
result, err := client.CalculateCart(ctx, cartReq)
if err != nil {
log.Fatal(err)
}
// Use a type switch to handle the polymorphic response
switch r := result.(type) {
case *models.CalculateCartResponse:
fmt.Printf("ZipTax cart total tax: %.2f\n", r.Items[0].LineItems[0].Tax.Amount)
case *models.TaxCloudCalculateCartResponse:
fmt.Printf("TaxCloud cart ID: %s\n", r.Items[0].CartID)
}After calculating a cart with TaxCloud credentials, convert it into a finalized order. This requires the cartId returned from a previous CalculateCart call:
req := &models.CreateOrderFromCartRequest{
CartID: "ce4a1234-5678-90ab-cdef-1234567890ab", // from CalculateCart response
OrderID: "my-order-1", // your internal order ID
}
order, err := client.CreateOrderFromCart(ctx, req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order created: %s\n", order.OrderID)To set a completed date on the order, use UpdateOrder after creation.
The SDK supports comprehensive order lifecycle management through the TaxCloud API. To use these features, you must configure TaxCloud credentials during client initialization.
Create a new order for tax filing:
orderReq := &models.CreateOrderRequest{
OrderID: "order-123",
CustomerID: "customer-456",
TransactionDate: "2024-01-15T09:30:00Z",
CompletedDate: "2024-01-15T09:30:00Z",
Origin: models.TaxCloudAddress{
Line1: "200 Spectrum Center Drive Suite 300",
City: "Irvine",
State: "CA",
Zip: "92618",
},
Destination: models.TaxCloudAddress{
Line1: "323 Washington Ave N",
City: "Minneapolis",
State: "MN",
Zip: "55401-2427",
},
LineItems: []models.CartItemWithTax{
{
Index: 0,
ItemID: "item-1",
Price: 10.8,
Quantity: 1.5,
Tax: models.Tax{
Amount: 1.31,
Rate: 0.0813,
},
},
},
Currency: &models.Currency{},
}
response, err := client.CreateOrder(ctx, orderReq)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order created: %s\n", response.OrderID)Retrieve an existing order by ID:
order, err := client.GetOrder(ctx, "order-123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order %s has %d line items\n", order.OrderID, len(order.LineItems))Update an order's completed date (when it was shipped/completed):
updateReq := &models.UpdateOrderRequest{
CompletedDate: "2024-01-16T10:00:00Z",
}
order, err := client.UpdateOrder(ctx, "order-123", updateReq)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order updated with new completed date: %s\n", order.CompletedDate)Create a full or partial refund against an order:
Partial Refund:
refundReq := &models.RefundTransactionRequest{
Items: []models.CartItemRefundWithTaxRequest{
{
ItemID: "item-1",
Quantity: 1.0,
},
},
}
refunds, err := client.RefundOrder(ctx, "order-123", refundReq)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Refunded %d items\n", len(refunds[0].Items))Full Refund:
// Empty request for full order refund
refundReq := &models.RefundTransactionRequest{}
refunds, err := client.RefundOrder(ctx, "order-123", refundReq)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Full order refund created\n")Note: An order can only be refunded once, regardless of whether it's a partial or full refund.
The SDK provides structured error types for proper error handling:
response, err := client.GetSalesTaxByAddress(ctx, address)
if err != nil {
// Check for validation errors
var validationErr *ziptax.ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation error: %s\n", validationErr.Message)
return
}
// Check for API errors
var apiErr *ziptax.APIError
if errors.As(err, &apiErr) {
fmt.Printf("API error: %s (code %d)\n", apiErr.Message, apiErr.Code)
return
}
// Check for sentinel errors
if errors.Is(err, ziptax.ErrInvalidAPIKey) {
fmt.Println("Invalid API key")
return
}
// Check for TaxCloud errors
if errors.Is(err, ziptax.ErrTaxCloudNotConfigured) {
fmt.Println("TaxCloud credentials not configured")
return
}
// Generic error
fmt.Printf("Error: %v\n", err)
return
}All API methods accept a context.Context for cancellation and timeouts:
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
response, err := client.GetSalesTaxByAddress(ctx, address)// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Cancel from another goroutine
go func() {
time.Sleep(100 * time.Millisecond)
cancel()
}()
response, err := client.GetSalesTaxByAddress(ctx, address)The client is safe for concurrent use:
var wg sync.WaitGroup
addresses := []string{"address1", "address2", "address3"}
for _, addr := range addresses {
wg.Add(1)
go func(address string) {
defer wg.Done()
resp, err := client.GetSalesTaxByAddress(ctx, address)
// Handle response...
}(addr)
}
wg.Wait()Core Options:
WithBaseURL(url string)- Set a custom API base URLWithHTTPClient(client *http.Client)- Use a custom HTTP clientWithTimeout(timeout time.Duration)- Set request timeoutWithMaxRetries(max int)- Set maximum retry attempts (0 to disable)WithRetryWait(min, max time.Duration)- Set retry backoff timesWithLogger(logger Logger)- Enable request/response loggingWithUserAgent(ua string)- Set a custom User-Agent header
TaxCloud Options:
WithTaxCloudConnectionID(id string)- Set TaxCloud Connection ID (required for order management)WithTaxCloudAPIKey(key string)- Set TaxCloud API Key (required for order management)WithTaxCloudBaseURL(url string)- Set custom TaxCloud API base URL
WithHistorical(date string)- Get historical rates (YYYYMM format, e.g., "202401")WithCountryCode(code string)- Specify country code (USA or CAN)WithFormat(format string)- Set response format (json or xml)
See the examples directory for complete examples:
- Basic Usage - Simple API calls
- Product Code Search - TIC search and AI recommendation
- Concurrent Usage - Parallel requests with goroutines
- Error Handling - Proper error handling patterns
- Context Timeout - Using context for timeouts and cancellation
- TaxCloud Order - TaxCloud order management operations
For detailed API documentation, see pkg.go.dev.
- Go 1.21 or higher
- Make (optional, for using Makefile commands)
# Run all tests
make test
# Run tests with coverage
make test-coverage
# Run tests with verbose output
make test-verbose# Format code
make fmt
# Organize imports
make imports
# Run linter
make lint
# Run all checks
make check# Build the SDK
make buildSee CHANGELOG.md for a detailed list of changes in each release.
This project follows Semantic Versioning. The version is defined in version.go.
- MAJOR: Breaking changes (incompatible API changes)
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
When submitting a PR, you must bump the version in version.go. See .github/VERSION_BUMP_GUIDE.md for detailed instructions.
The GitHub Actions workflow will automatically verify that the semantic version has been properly bumped.
Contributions are welcome! Please feel free to submit a Pull Request.
Before submitting:
- Update the version in
version.gofollowing semantic versioning - Update
CHANGELOG.mdwith your changes - Add tests for new functionality
- Update documentation (README, GoDoc comments)
- Ensure all tests pass (
make test) - Run linter (
make lint)
This project is licensed under the MIT License - see the LICENSE file for details.
For API-related questions and support, please contact support@zip.tax.
For SDK issues, please open an issue on GitHub.