LookupTax SDK for Python
Validate tax IDs — VAT, EIN, GSTIN, ABN and more — against official government registries.
pip install lookuptax
Python 3.9+.
Quick start
from lookuptax import LookupTax
with LookupTax(api_key="...") as lookuptax:
res = lookuptax.validate("IE", "53057102A")
if res.result.status == "VALID":
print("Confirmed:", res.entity.name)
Read result.status, not isValid
Every response carries a result whose status is the single source of truth. Five mutually exclusive values:
result.status |
Meaning |
|---|---|
VALID |
Format passed and a government registry confirmed the entity. |
INVALID |
Definitive negative — bad structure/check digit, or no registry record. |
UNVERIFIED |
No queryable registry for that country, so only the format was checked. A pass is not proof the business exists. |
INDETERMINATE |
A registry was temporarily unreachable. Retry — do not treat as invalid. |
UNSUPPORTED |
We don't validate that country or number type yet. |
Do not branch on validation["overall"]["isValid"]: it is true for VALID, UNVERIFIED and INDETERMINATE alike, so a format-only pass looks identical to a registry-confirmed one.
Two helpers encode that correctly:
res.result.is_valid # True only for VALID — UNVERIFIED is not "valid"
res.result.should_retry # True only for INDETERMINATE
match res.result.status:
case "VALID": accept(res.entity)
case "INVALID": reject(res.result.reason)
case "UNVERIFIED": accept_with_caveat() # format checked only
case "INDETERMINATE": retry_later() # registry was down
case "UNSUPPORTED": skip()
Errors vs outcomes
The SDK mirrors the API's split:
- Validation outcomes return. An
INVALIDtax ID is a successful call — you get a response, not an exception.UNSUPPORTEDreturns too, even though it arrives as HTTP 422, because the body still carries aresult. - Request errors raise. Authentication, quota, rate limit, malformed request — the call could not be processed at all.
from lookuptax import LookupTaxError, QuotaExceededError, RateLimitError
try:
res = lookuptax.validate("IE", "53057102A")
except RateLimitError as e:
time.sleep(e.retry_after or 1)
except QuotaExceededError:
notify_billing()
except LookupTaxError as e:
log.error("%s (%s)", e.code, e.status)
Branch on e.code (a stable machine-readable string), never on the message.
Transient failures — 429, 500, 503 — retry automatically (2 attempts by default, honouring Retry-After). Pass max_retries=0 to opt out. Batch submission is never retried automatically, because a retried submission that actually succeeded would create a second batch and reserve quota twice.
EU routing
lookuptax.validate("DE", "DE123456789", validation_source="vies") # VIES only
lookuptax.validate("DE", "DE123456789", validation_source="local") # national registry only
With vies or local there is no fallback to the other — if the requested source cannot answer you get INDETERMINATE rather than a silent answer from the other one, which is what makes it usable for reconciliation. Default is auto. Ignored outside VIES-covered countries.
Countries needing a name
lookuptax.validate("MX", "XAXX010101000", additional_params={"name": "Empresa Ejemplo SA de CV"})
Batch
Up to 100 IDs, validated asynchronously. Enterprise plan only.
from lookuptax import TaxIdInput
batch = lookuptax.create_batch([
TaxIdInput("IE", "53057102A"),
TaxIdInput("MX", "XAXX010101000", name="Empresa Ejemplo SA de CV"),
])
done = lookuptax.wait_for_batch(batch["batch_id"])
for item in lookuptax.iterate_batch_results(batch["batch_id"]):
print(item.tin, item.validation_result.result.status if item.validation_result else item.status)
wait_for_batch polls on a relaxed cadence by design. When a registry is unreachable an item keeps retrying for roughly three days, carrying a next_retry_at — a long-running item is not a stuck one.
Configuration
LookupTax(
api_key="...", # required
base_url="https://api.lookuptax.com/v1", # default
timeout=30.0, # seconds, per request
max_retries=2, # 429/500/503 only
client=httpx.Client(...), # inject a proxy/transport, or for tests
)
base_url is configurable because the published path prefix is deployment configuration rather than a property of the SDK.
API
| Method | Endpoint |
|---|---|
validate(country_iso, tin, ...) |
GET /validate |
create_batch(tax_ids, ...) |
POST /batch |
get_batch(batch_id, ...) |
GET /batch/{id} |
list_batches(...) |
GET /batch |
cancel_batch(batch_id) |
POST /batch/{id}/cancel |
delete_batch(batch_id) |
DELETE /batch/{id} |
get_tax_id(request_id) |
GET /batch/tax-id/{id} |
wait_for_batch(batch_id, ...) |
polls to a terminal state |
iterate_batch_results(batch_id, ...) |
generator over every page |
License
MIT
Metadata
Release files for lookuptax 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lookuptax-1.0.1.tar.gz | 11.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lookuptax-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.8 kB
Release files / lookuptax-1.0.1.tar.gz
| Download URL | lookuptax-1.0.1.tar.gz |
|---|---|
| Size | 11.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cfabe92bd6e75ec65a7d5c04f70d3b831714fa3294b622aff20832e32a348496
|
|
BLAKE2b-256 checksum How to use checksums |
37f96e88a4511b535258f62565a38575999b4e0d22bd7b22aa319038b7ed6922
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Release files / lookuptax-1.0.1-py3-none-any.whl
| Download URL | lookuptax-1.0.1-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b2e5f3c3ff2b3e632f944e305aac933b7d51033de4fc5ea6ee4d275d4950a7b5
|
|
BLAKE2b-256 checksum How to use checksums |
5e88e0b9c327440f9bb92dd0c6ee0e7c2fe922e7e5018c4e5f973965b63843a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Security for lookuptax 1.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.