Hardcoding VAT rate tables is a maintenance liability. When Estonia raised its standard rate from 20% to 22% in January 2024, every application with a hardcoded rate table was suddenly billing incorrectly — without any failing tests to catch it. A VAT rates API solves this by making rate data a runtime lookup rather than a compile-time constant.
Getting Started: Basic Rate Lookup
# Basic rate lookup — no body needed, just query param
curl 'https://api.taxid.dev/v1/rates?country=DE' \
-H 'Authorization: Bearer YOUR_API_KEY'
# Response
{
"country": "DE",
"standard_rate": 19,
"reduced_rates": [7],
"super_reduced_rate": null,
"parking_rate": null,
"last_updated": "2026-01-01"
}class VatRatesClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.cache = new Map();
this.cacheTtl = 24 * 60 * 60 * 1000; // 24 hours
}
async getRates(countryCode) {
const cached = this.cache.get(countryCode);
if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
return cached.data;
}
const res = await fetch(
`https://api.taxid.dev/v1/rates?country=${countryCode}`,
{ headers: { 'Authorization': `Bearer ${this.apiKey}` } }
);
if (!res.ok) throw new Error(`Rate lookup failed: ${res.status}`);
const data = await res.json();
this.cache.set(countryCode, { data, timestamp: Date.now() });
return data;
}
async getStandardRate(countryCode) {
const rates = await this.getRates(countryCode);
return rates.standard_rate;
}
}import os
import time
import requests
from functools import lru_cache
class VatRatesClient:
def __init__(self):
self.api_key = os.environ['TAXID_API_KEY']
self.base_url = 'https://api.taxid.dev/v1'
self._cache = {}
self._cache_ttl = 86400 # 24 hours
def get_rates(self, country_code: str) -> dict:
cached = self._cache.get(country_code)
if cached and time.time() - cached['timestamp'] < self._cache_ttl:
return cached['data']
response = requests.get(
f'{self.base_url}/rates',
params={'country': country_code},
headers={'Authorization': f'Bearer {self.api_key}'},
timeout=5
)
response.raise_for_status()
data = response.json()
self._cache[country_code] = {'data': data, 'timestamp': time.time()}
return dataCaching Strategy
VAT rates change rarely — typically once or twice per year at most, and always with advance notice. Cache rate responses for 24 hours. A stale-while-revalidate pattern works well: serve the cached rate immediately while refreshing it in the background.
Tip
Monitor the last_updated field in API responses. If a rate's last_updated date is more recent than your cache, force-invalidate and refresh. This gives you near-instant rate updates without polling.
Error Handling
- →Always set a timeout (5 seconds recommended) — never let a rate lookup block checkout indefinitely
- →Fall back to a cached rate if the API is temporarily unavailable
- →Log all rate lookup errors with the country code and timestamp for debugging
- →Never silently default to 0% on error — charge the standard rate as a safe fallback
Start validating EU VAT numbers
Free plan — 100 validations/month. No credit card required.