Official Python SDK for the Ziptax API - Get accurate sales and use tax rates for any US or Canadian address.
- 🚀 Simple and intuitive API
- 🔄 Automatic retry logic with exponential backoff
- ✅ Input validation
- 🔍 Type hints for better IDE support
- 📦 Pydantic models for response validation
- 🔒 Comprehensive error handling
- ⚡ Support for concurrent operations
- 🧪 Well-tested with high code coverage
pip install ziptax-sdkfrom ziptax import ZipTaxClient
# Initialize the client with your API key
client = ZipTaxClient.api_key("your-api-key-here")
# Get sales tax by address
response = client.request.GetSalesTaxByAddress(
"200 Spectrum Center Drive, Irvine, CA 92618"
)
print(f"Address: {response.addressDetail.normalizedAddress}")
if response.tax_summaries:
for summary in response.tax_summaries:
print(f"{summary.summary_name}: {summary.rate * 100:.2f}%")
# Always close the client when done
client.close()from ziptax import ZiptaxClient
# Basic initialization
client = ZiptaxClient.api_key("your-api-key-here")
# With custom configuration
client = ZiptaxClient.api_key(
"your-api-key-here",
timeout=60, # Request timeout in seconds
max_retries=5, # Maximum retry attempts
retry_delay=2.0, # Base delay between retries
)
# Using as a context manager (recommended)
with ZiptaxClient.api_key("your-api-key-here") as client:
response = client.request.GetSalesTaxByAddress("123 Main St")response = client.request.GetSalesTaxByAddress(
address="200 Spectrum Center Drive, Irvine, CA 92618",
country_code="USA", # Optional: "USA" or "CAN" (default: "USA")
historical="2024-01", # Optional: Historical date (YYYY-MM format)
format="json", # Optional: "json" or "xml" (default: "json")
)
# Access response data
print(response.addressDetail.normalizedAddress)
print(response.addressDetail.geoLat)
print(response.addressDetail.geoLng)
# Response code
print(f"Response: {response.metadata.response.code} - {response.metadata.response.message}")
# Tax summaries with display rates
if response.tax_summaries:
for summary in response.tax_summaries:
print(f"{summary.summary_name}: {summary.rate}")
for display_rate in summary.display_rates:
print(f" {display_rate.name}: {display_rate.rate}")
# Base rates by jurisdiction
if response.base_rates:
for rate in response.base_rates:
print(f"{rate.jur_name} ({rate.jur_type}): {rate.rate}")
# Sourcing rules
if response.sourcing_rules:
print(f"Sourcing: {response.sourcing_rules.value}")response = client.request.GetSalesTaxByGeoLocation(
lat="33.6489",
lng="-117.8386",
country_code="USA",
format="json",
)
print(response.addressDetail.normalizedAddress)metrics = client.request.GetAccountMetrics()
print(f"Core Requests: {metrics.core_request_count:,} / {metrics.core_request_limit:,}")
print(f"Core Usage: {metrics.core_usage_percent:.2f}%")
print(f"Geo Requests: {metrics.geo_request_count:,} / {metrics.geo_request_limit:,}")
print(f"Geo Usage: {metrics.geo_usage_percent:.2f}%")
print(f"Account Active: {metrics.is_active}")You can configure the client using dict-style access:
client = ZiptaxClient.api_key("your-api-key-here")
# Set configuration options
client.config["format"] = "json"
client.config["timeout"] = 60
# Get configuration options
timeout = client.config["timeout"]The SDK provides comprehensive error handling with specific exception types:
from ziptax import (
ZipTaxClient,
ZipTaxValidationError,
ZipTaxAuthenticationError,
ZipTaxRateLimitError,
ZipTaxServerError,
ZipTaxError,
)
client = ZipTaxClient.api_key("your-api-key-here")
try:
response = client.request.GetSalesTaxByAddress("123 Main St")
except ZipTaxValidationError as e:
# Input validation errors
print(f"Validation error: {e.message}")
except ZipTaxAuthenticationError as e:
# Authentication failures (401)
print(f"Authentication error: {e.message}")
except ZipTaxRateLimitError as e:
# Rate limit exceeded (429)
print(f"Rate limit error: {e.message}")
if e.retry_after:
print(f"Retry after {e.retry_after} seconds")
except ZipTaxServerError as e:
# Server errors (5xx)
print(f"Server error: {e.message}")
except ZipTaxError as e:
# General Ziptax errors
print(f"Ziptax error: {e.message}")ZipTaxError
├── ZipTaxAPIError
│ ├── ZipTaxAuthenticationError (401)
│ ├── ZipTaxAuthorizationError (403)
│ ├── ZipTaxNotFoundError (404)
│ ├── ZipTaxRateLimitError (429)
│ └── ZipTaxServerError (5xx)
├── ZipTaxValidationError
├── ZipTaxConnectionError
├── ZipTaxTimeoutError
└── ZipTaxRetryError
For concurrent operations, you can use asyncio with the SDK:
import asyncio
from concurrent.futures import ThreadPoolExecutor
from ziptax import ZipTaxClient
async def get_tax_rates_async(client, addresses):
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
tasks = [
loop.run_in_executor(
executor,
client.request.GetSalesTaxByAddress,
address
)
for address in addresses
]
return await asyncio.gather(*tasks)
# Usage
client = ZipTaxClient.api_key("your-api-key-here")
addresses = ["123 Main St, CA", "456 Oak Ave, NY"]
responses = asyncio.run(get_tax_rates_async(client, addresses))See examples/async_usage.py for more examples.
All API responses are validated using Pydantic models:
class V60Response:
metadata: V60Metadata # Response metadata with code/message
base_rates: Optional[List[V60BaseRate]] # Tax rates by jurisdiction
service: V60Service # Service taxability
shipping: V60Shipping # Shipping taxability
sourcing_rules: Optional[V60SourcingRules] # Origin/Destination rules
tax_summaries: Optional[List[V60TaxSummary]] # Tax summaries with display rates
addressDetail: V60AddressDetail # Address detailsclass V60Metadata:
version: str # API version (e.g., "v60")
response: V60ResponseInfo # Response info object
class V60ResponseInfo:
code: int # Response code (100 = success)
name: str # Response code name
message: str # Response message
definition: str # Schema definition URLclass V60TaxSummary:
rate: float # Summary tax rate
tax_type: str # Tax type (e.g., "SALES_TAX")
summary_name: str # Summary description
display_rates: List[V60DisplayRate] # Display rates breakdown
class V60DisplayRate:
name: str # Display rate name
rate: float # Display rate valueclass V60AccountMetrics:
core_request_count: int
core_request_limit: int
core_usage_percent: float
geo_enabled: bool
geo_request_count: int
geo_request_limit: int
geo_usage_percent: float
is_active: bool
message: strSee the models documentation for complete model definitions.
# Clone the repository
git clone https://github.com/ziptax/ziptax-python.git
cd ziptax-python
# Install dependencies
pip install -e ".[dev]"# Run all tests
pytest
# Run with coverage
pytest --cov=src/ziptax --cov-report=html
# Run specific test file
pytest tests/test_client.py# Format code
black src/ tests/
# Lint code
ruff src/ tests/
# Type checking
mypy src/See the examples/ directory for complete examples:
- basic_usage.py - Basic SDK usage
- async_usage.py - Concurrent operations
- error_handling.py - Error handling patterns
Main client for interacting with the Ziptax API.
api_key(api_key, **kwargs)- Create a client instance with an API keyclose()- Close the HTTP client session
config- Configuration object (dict-like access)request- Functions object for making API requests
API endpoint functions accessible via client.request.
GetSalesTaxByAddress(address, **kwargs)- Get tax rates by addressGetSalesTaxByGeoLocation(lat, lng, **kwargs)- Get tax rates by coordinatesGetAccountMetrics(**kwargs)- Get account usage metrics
- Python 3.8+
- requests >= 2.28.0
- pydantic >= 2.0.0
This project is licensed under the MIT License - see the LICENSE file for details.
- Documentation: https://github.com/ziptax/ziptax-python#readme
- Issues: https://github.com/ziptax/ziptax-python/issues
- Email: support@zip.tax
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CHANGELOG.md for version history and changes.
Made with ❤️ by the Ziptax Team