duty27-eu-vat
The standard VAT rate in every EU member state, on any date since 1 January 2016. No dependencies, works offline, fully typed. Python 3.9 and later.
from duty27_eu_vat import get_standard_rate
get_standard_rate("DE") # today's rate: Decimal('19')
get_standard_rate("DE", "2020-07-01") # Decimal('16'), Germany's temporary cut
get_standard_rate("FI", "2024-09-01") # Decimal('25.5')
Rates change, sometimes for a few months only, and a hardcoded table silently goes wrong. This package keeps
the dated history in one place so "what was the rate on the day of this sale?" has a real answer. Rates are
exact Decimal values, not floats, so price * rate / 100 has no rounding noise.
Install
pip install duty27-eu-vat
Use
from datetime import date
from duty27_eu_vat import (
get_standard_rate, get_rate_history, get_rate_changes, list_countries, format_rate, DATA_AS_OF,
)
get_standard_rate("GR", date(2016, 6, 1)) # Decimal('24'); GR is accepted for Greece (the EU code is EL)
get_rate_history("DE")
# [RateWindow(effective_from=datetime.date(2016, 1, 1), rate=Decimal('19')),
# RateWindow(effective_from=datetime.date(2020, 7, 1), rate=Decimal('16')),
# RateWindow(effective_from=datetime.date(2021, 1, 1), rate=Decimal('19'))]
get_rate_changes(since="2025-01-01") # RateChange(country='RO', date=date(2025, 8, 1), from_rate=19, to_rate=21), ...
list_countries() # the 27 member states: Country(code='AT', name='Austria'), ...
format_rate(Decimal("25.5"), "de") # '25,5 %'
DATA_AS_OF # the date the data was last checked
| Function | Returns |
|---|---|
get_standard_rate(country, on=None) |
The rate in force on a date (date, datetime, or "YYYY-MM-DD"). Today (UTC) if omitted. A date after the last known change returns the latest known rate. |
get_rate_history(country) |
Every rate the country has had since 2016, in order. |
get_rate_changes(country=None, since=None) |
Every change, newest first. |
list_countries() |
The 27 member states, with EU codes (Greece is EL; GR is accepted) and English names. |
format_rate(rate, locale="en") |
The rate as a localised percentage. Supports en, de, fr, es. |
normalize_country(code) |
The EU code for a country code, case-insensitive. |
Errors are never guesses. An unknown or non-EU country raises UnknownCountryError; a date before 2016-01-01
raises DateOutOfRangeError; a malformed date string raises ValueError; a date of the wrong type raises
TypeError. (UnknownCountryError and DateOutOfRangeError are both ValueErrors.) An aware datetime is
converted to UTC first; a naive one is read as its own calendar day.
What it covers, and what it does not
- Standard rates only. Reduced rates (for example e-books, newspapers and periodicals) are not included.
- Lookup, not calculation. It returns the rate. It does not work out net or gross amounts, rounding, whether a sale is reverse-charged, or OSS reports: those are in the Duty27 API (see below).
- 27 EU member states, from 2016-01-01.
- The data has an age.
DATA_AS_OFis the date it was last compared with the rates Duty27 publishes. A rate that changed after that date will not be here until a new version is released. For anything that must be right today, check the source, or use the API below. - The rates are compiled from the European Commission's TEDB service and cross-checked against the Commission's own historical rate tables and national sources. Every change since 2016 cites the law or tax-authority notice that made it, and the 2016 starting rates cite the Commission's rate table. The citations are not bundled in this package: they are listed at https://duty27.com/vat-rates/history, and the CSV and JSON there carry them for every row.
This package provides standard VAT rate data for information. It is not tax advice.
Need more than a rate?
Knowing the rate is the easy part. Deciding what to charge, and keeping the proof, is the rest: whether a sale is B2B and reverse-charged, whether the EU €10,000 threshold has been passed, whether a VAT number is valid, and an archive and OSS report for your filings. That is what the Duty27 API does, and it is free to try (500 calculations a month, no credit card).
- Sign up: https://duty27.com/signup
- API docs: https://duty27.com/docs
- Guides: B2B sales and reverse charge, checking a VAT number with VIES, OSS registration and returns, the €10,000 threshold
- Reduced-rate categories (e-books, newspapers, periodicals) are available through the API.
Working on it
This folder is self-contained: PYTHONPATH=src python -m unittest discover -s tests runs the tests with nothing to
install. The rate data in src/duty27_eu_vat/_data.py and the test file tests/test-vectors.csv are generated from
the shared source at the repository root (./build.sh data), not edited by hand.
Licence
Metadata
Release files for duty27-eu-vat 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| duty27_eu_vat-0.1.0.tar.gz | 9.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| duty27_eu_vat-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.4 kB
Release files / duty27_eu_vat-0.1.0.tar.gz
| Download URL | duty27_eu_vat-0.1.0.tar.gz |
|---|---|
| Size | 9.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0179ecaecae98d88ec20be9e1bc0db3fe8f907d36f889f8e6cd211e1fafd07cd
|
|
BLAKE2b-256 checksum How to use checksums |
ae895cd10983dd15cf00c85214770ad9b2865569c462a1e19e1578a586c65be7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / duty27_eu_vat-0.1.0-py3-none-any.whl
| Download URL | duty27_eu_vat-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c340985610c04695d39b666086bb1feeaf5fde369e19ab0a12d64e6ee82cb9e2
|
|
BLAKE2b-256 checksum How to use checksums |
eb04910efe84bd0fb1cdbb13dda580b687b889649a02df86500dc70477779723
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log