Official Python SDK for the Vatly VAT validation API
Project description
vatly
Official Python SDK for the Vatly VAT validation API. Validate EU, UK, Swiss, Norwegian, and Australian VAT/GST numbers and look up VAT rates by country. See the full API reference.
Installation
pip install vatly
Quick Start
from vatly import Vatly
vatly = Vatly("vtly_live_...")
result = vatly.vat.validate("NL123456789B01")
print(result.data.valid) # True
if result.data.company:
print(result.data.company.name)
Usage
vatly.vat.validate()
Validate a single VAT number.
result = vatly.vat.validate(
"NL123456789B01",
requester_vat_number="DE987654321", # optional, for consultation number
cache=False, # optional, bypass 30-day cache
request_id="my-trace-id", # optional, for request tracing
)
print(result.data.valid) # True
print(result.data.vat_number) # "NL123456789B01"
print(result.data.country_code) # "NL"
print(result.data.company.name) # "Example BV"
print(result.data.company.address) # "Amsterdam, Netherlands" or None
print(result.data.consultation_number) # None or string (EU/UK only)
print(result.data.requested_at) # "2026-03-18T12:00:00Z"
print(result.meta.request_id) # "req_abc123"
print(result.meta.cached) # True/False/None
print(result.meta.stale) # True/False/None
print(result.meta.source_status) # "live", "unavailable", "degraded", or None
print(result.rate_limit.remaining) # 99
print(result.rate_limit.burst_limit) # int or None
vatly.vat.validate_batch()
Validate up to 50 VAT numbers in a single request.
from vatly import is_batch_success
result = vatly.vat.validate_batch(
["NL123456789B01", "DE987654321", "XX000"],
requester_vat_number="DE987654321", # optional
cache=False, # optional
request_id="my-trace-id", # optional
)
print(result.summary.total) # 3
print(result.summary.succeeded) # 2
print(result.summary.failed) # 1
for item in result.results:
if is_batch_success(item):
print(f"{item.data.vat_number} is {'valid' if item.data.valid else 'invalid'}")
else:
print(f"{item.meta.vat_number} failed: {item.error.message}")
vatly.async_vat.validate()
Submit a VAT number for async validation. Results are delivered via webhook. Requires a Pro or Business plan and a configured webhook URL.
# Sync client
response = client.async_vat.validate("DE123456789")
print(response.data.request_id) # Track this ID
print(response.data.status) # "pending"
# Async client
response = await client.async_vat.validate("DE123456789")
vatly.async_vat.validate_batch()
Submit multiple VAT numbers for async validation.
response = client.async_vat.validate_batch(
["DE123456789", "NL987654321B01"],
requester_vat_number="NL987654321B01", # optional
)
print(response.data.batch_id) # Track this ID
print(response.data.accepted) # Number queued
print(response.data.rejected) # Items with invalid format
vatly.rates.list()
List VAT rates for all supported countries.
result = vatly.rates.list()
for rate in result.data:
print(f"{rate.country_name}: {rate.standard_rate}%")
vatly.rates.get(country_code)
Get VAT rates for a specific country.
result = vatly.rates.get("NL")
print(result.data.standard_rate) # 21
print(result.data.other_rates) # [OtherRate(rate=9, type="reduced"), ...]
Async Usage
from vatly import AsyncVatly
async with AsyncVatly("vtly_live_...") as vatly:
result = await vatly.vat.validate("NL123456789B01")
print(result.data.valid)
rates = await vatly.rates.list()
for rate in rates.data:
print(f"{rate.country_name}: {rate.standard_rate}%")
Error Handling
The SDK raises typed exceptions for all error conditions. Use try/except with specific exception classes:
from vatly import (
Vatly,
VatlyError,
AuthenticationError,
ValidationError,
RateLimitError,
UpstreamError,
)
vatly = Vatly("vtly_live_...")
try:
result = vatly.vat.validate("INVALID")
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after}s")
except UpstreamError as e:
print(f"Tax authority unavailable. Retry after {e.retry_after}s")
except AuthenticationError as e:
print("Invalid API key or insufficient plan")
except ValidationError as e:
print(f"Invalid input: {e.message}")
if e.details:
for d in e.details:
print(f" {d['field']}: {d['message']}")
except VatlyError as e:
print(e.message, e.code, e.status_code)
Error Classes
| Class | Trigger Codes |
|---|---|
AuthenticationError |
unauthorized, tier_insufficient, forbidden, key_revoked |
ValidationError |
invalid_vat_format, missing_parameter, validation_error, invalid_json |
RateLimitError |
rate_limit_exceeded, burst_limit_exceeded |
UpstreamError |
upstream_unavailable, upstream_member_state_unavailable |
VatlyError |
Base class for all errors, including timeout, network_error, parse_error, internal_error, key_limit_reached |
Error Properties
e.message # Human-readable message
e.code # Machine-readable code (e.g. "unauthorized", "rate_limit_exceeded")
e.status_code # HTTP status (0 for network/timeout errors)
e.request_id # Request ID (string or None)
e.docs_url # Link to error documentation (string, empty if not provided)
e.details # Validation error details (list of dicts or None)
Retries
The SDK does not retry automatically. RateLimitError and UpstreamError include a retry_after property (seconds) when the server provides one.
Test Mode
Use test API keys (vtly_test_*) to validate without hitting real tax authorities.
vatly = Vatly("vtly_test_...")
result = vatly.vat.validate("NL123456789B01")
print(result.meta.mode) # "test"
| Magic VAT Number | Result |
|---|---|
NL123456789B01 |
Valid, with company info |
XX000000000 |
Invalid format error |
Configuration
# String API key
vatly = Vatly("vtly_live_...")
# Keyword arguments
vatly = Vatly(
api_key="vtly_live_...",
base_url="https://api.vatly.dev", # default
timeout=30.0, # seconds, default
)
# Environment variable fallback
# Set VATLY_API_KEY=vtly_live_... and omit the key:
vatly = Vatly()
The client also supports context managers for proper resource cleanup:
with Vatly("vtly_live_...") as vatly:
result = vatly.vat.validate("NL123456789B01")
Type Hints
The package includes a py.typed marker (PEP 561) for full type checking support.
from vatly import (
Vatly,
AsyncVatly,
ValidateResponse,
BatchValidateResponse,
BatchResultSuccess,
BatchResultError,
Company,
VatValidationResult,
ResponseMeta,
RateLimitInfo,
VatRate,
OtherRate,
ListRatesResponse,
GetRateResponse,
BatchSummary,
is_batch_success,
)
Requirements
- Python >= 3.9
- httpx >= 0.27 (sole runtime dependency)
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vatly-0.3.0.tar.gz.
File metadata
- Download URL: vatly-0.3.0.tar.gz
- Upload date:
- Size: 20.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72689478471202d4dee1e57d8b4ef4791c11f70bd2ba3eb0b6ba60cd1c6e789a
|
|
| MD5 |
5abebcea88fe52f3d305af6c3de93d2b
|
|
| BLAKE2b-256 |
39ba30c37c8d03b93a02a17660f7f8aa8ca3af646b6142db2b7458da5e30514f
|
Provenance
The following attestation bundles were made for vatly-0.3.0.tar.gz:
Publisher:
publish.yml on getvatly/vatly-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vatly-0.3.0.tar.gz -
Subject digest:
72689478471202d4dee1e57d8b4ef4791c11f70bd2ba3eb0b6ba60cd1c6e789a - Sigstore transparency entry: 1201081084
- Sigstore integration time:
-
Permalink:
getvatly/vatly-python@55812f051a73589a8b97035cd5943a89022929e2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/getvatly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55812f051a73589a8b97035cd5943a89022929e2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file vatly-0.3.0-py3-none-any.whl.
File metadata
- Download URL: vatly-0.3.0-py3-none-any.whl
- Upload date:
- Size: 17.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30c95531d4e340e19df98265b3a154ad67136dedea71e5ea08cf56bfc2384dbc
|
|
| MD5 |
a1ac462259c951bbea6b10b6cfdfb51c
|
|
| BLAKE2b-256 |
a1f7d09be87616e0dd2f0d47c5c74953562e4e47410157d048367cd845ba042a
|
Provenance
The following attestation bundles were made for vatly-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on getvatly/vatly-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vatly-0.3.0-py3-none-any.whl -
Subject digest:
30c95531d4e340e19df98265b3a154ad67136dedea71e5ea08cf56bfc2384dbc - Sigstore transparency entry: 1201081093
- Sigstore integration time:
-
Permalink:
getvatly/vatly-python@55812f051a73589a8b97035cd5943a89022929e2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/getvatly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55812f051a73589a8b97035cd5943a89022929e2 -
Trigger Event:
release
-
Statement type: