Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vatly-0.3.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vatly-0.3.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

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

Hashes for vatly-0.3.0.tar.gz
Algorithm Hash digest
SHA256 72689478471202d4dee1e57d8b4ef4791c11f70bd2ba3eb0b6ba60cd1c6e789a
MD5 5abebcea88fe52f3d305af6c3de93d2b
BLAKE2b-256 39ba30c37c8d03b93a02a17660f7f8aa8ca3af646b6142db2b7458da5e30514f

See more details on using hashes here.

Provenance

The following attestation bundles were made for vatly-0.3.0.tar.gz:

Publisher: publish.yml on getvatly/vatly-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for vatly-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 30c95531d4e340e19df98265b3a154ad67136dedea71e5ea08cf56bfc2384dbc
MD5 a1ac462259c951bbea6b10b6cfdfb51c
BLAKE2b-256 a1f7d09be87616e0dd2f0d47c5c74953562e4e47410157d048367cd845ba042a

See more details on using hashes here.

Provenance

The following attestation bundles were made for vatly-0.3.0-py3-none-any.whl:

Publisher: publish.yml on getvatly/vatly-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page