EU VAT rates API
A VAT rates API returns, for a member state, the rates it applies, so that a checkout, an invoice or an OSS return can be computed without a hand-kept table. Veriport returns the standard, reduced, super-reduced and parking rates of the 27 member states, the rate for seven usual categories (digital services, e-books, printed books, food, hotels, restaurants, newspapers), and on every row the day it was verified (table verified 2026-10-24, next review 2026-11-24) and the official pages it was read from. No other rates API dates and sources its rows.
· Reviewed monthly: last read 2026-10-28, next review 2026-11-24
verifiedOnandsources[]on every answer: you can show an auditor where a rate came from and when it was last read; a rate that changed in the last two years carries the date it started applying.- Category hints are hints for the usual case: the classification of a given supply is the seller’s, and the note says where a category splits (food 12 %, drinks 21 % in Belgian restaurants).
- The same table checks every row of an OSS return (
POST /vat/oss/return) and feeds the free lookup and the 27 country pages: one source, three surfaces. - Prices: $2.90 to $4.50 per 1,000 lookups, or cache the 27 rows once a day and pay nothing: 100 free calls a month cover a daily refresh.
The answer, field by field
France:
| Field | What it says |
|---|---|
country, name, currency | The member state (EL for Greece) and its currency. |
standard, reduced[], superReduced, parking | The rates in percent; null where a kind does not exist in that state. |
hints[] | Per category: rate, kind (standard, reduced, super_reduced, zero), since when it changed recently, note where the category splits. |
verifiedOn | The day this row was last read against the sources; the oldest row of the table is what the docs call "table verified". |
sources[] | The official pages this row was read from, with the day they were consulted. |
note | Territories outside the table (Corsica, the Canary Islands, Madeira…) and other caveats. |
{
"country": "FR",
"name": "France",
"currency": "EUR",
"standard": 20,
"reduced": [
5.5,
10
],
"superReduced": 2.1,
"parking": null,
"hints": [
{
"category": "digital_services",
"rate": 20,
"kind": "standard"
},
{
"category": "ebooks",
"rate": 5.5,
"kind": "reduced"
},
{
"category": "newspapers",
"rate": 2.1,
"kind": "super_reduced"
}
],
"verifiedOn": "2026-10-24",
"sources": [
{
"title": "VAT rates applied in the Member States of the European Union",
"url": "https://taxation-customs.ec.europa.eu/taxation/vat/vat-rates_en",
"consulted": "2026-10-24"
}
],
"note": "Corsica and the overseas departments apply other rates (not in this table)."
}By category, across the Union
GET /vat/rates?category=ebooks returns the e-book rate of every member state in one call, the usual question of a digital seller under OSS; the same for digital_services, printed_books, foodstuffs, hotel_accommodation, restaurant, newspapers.
curl "https://veriport.io/api/v1/vat/rates?category=ebooks"No upstream, a review instead of a degraded path
The table is in the service: nothing is fetched at request time, so the endpoint cannot be down because an administration is. What can be wrong is the table, which is why every row is dated, sourced and reviewed monthly, and why a change is announced in the changelog with the row it moved. The method is written on its own page.
The same call in six languages
One GET, the key optional for the first 20 calls a day.
curl "https://veriport.io/api/v1/vat/rates/FR"
# with a key, once you have one:
# curl -H "Authorization: Bearer vp_live_YOUR_KEY" "https://veriport.io/api/v1/vat/rates/FR"const KEY = process.env.VERIPORT_KEY; // optional for the first calls of the day
const res = await fetch("https://veriport.io/api/v1/vat/rates/FR", { headers: KEY ? { Authorization: "Bearer " + KEY } : {} });
const body = await res.json();
if (!res.ok) {
// RFC 9457 problem document: body.code is stable, body.detail is for humans
throw new Error(body.code + ": " + body.detail);
}
console.log(body.standard, body.verifiedOn);import os, requests
key = os.environ.get("VERIPORT_KEY") # optional for the first calls of the day
r = requests.get("https://veriport.io/api/v1/vat/rates/FR", headers={"Authorization": f"Bearer {key}"} if key else {}, timeout=15)
body = r.json()
if not r.ok:
# RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
raise RuntimeError(f"{body.get('code')}: {body.get('detail')}")
print(body["standard"], body["verifiedOn"])<?php
$key = getenv("VERIPORT_KEY") ?: null; // optional for the first calls of the day
$ch = curl_init("https://veriport.io/api/v1/vat/rates/FR");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => $key ? ["Authorization: Bearer $key"] : []]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
// RFC 9457 problem document: $body["code"] is stable, $body["detail"] is for humans
throw new RuntimeException(($body["code"] ?? "error") . ": " . ($body["detail"] ?? ""));
}
var_dump($body["standard"], $body["verifiedOn"]);package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://veriport.io/api/v1/vat/rates/FR", nil)
if key := os.Getenv("VERIPORT_KEY"); key != "" { // optional for the first calls of the day
req.Header.Set("Authorization", "Bearer "+key)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var body map[string]any
json.NewDecoder(res.Body).Decode(&body)
if res.StatusCode >= 400 {
// RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
panic(fmt.Sprintf("%v: %v", body["code"], body["detail"]))
}
fmt.Println(body["standard"], body["verifiedOn"])
}using System.Net.Http.Headers;
using System.Text.Json;
var http = new HttpClient();
var key = Environment.GetEnvironmentVariable("VERIPORT_KEY"); // optional for the first calls of the day
if (!string.IsNullOrEmpty(key)) http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
var res = await http.GetAsync("https://veriport.io/api/v1/vat/rates/FR");
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
// RFC 9457 problem document: "code" is stable, "detail" is for humans
throw new Exception($"{body.RootElement.GetProperty("code")}: {body.RootElement.GetProperty("detail")}");
}
Console.WriteLine(body.RootElement.GetProperty("standard") + " " + body.RootElement.GetProperty("verifiedOn"));Price per 1,000 calls, against the named alternatives
Competitors’ figures are read from their public pricing pages on 2026-10-28 at full use of the included volume; a range means several tiers. Ours include the tax of your country, charged by Stripe as merchant of record.
| Offer | Price | Included | Per 1,000 | Billing |
|---|---|---|---|---|
| vatlayer, Abstract, VATSense | monthly tiers (rates bundled with validation) | per tier | $1.65 → $14.00 | Subscription; rows undated |
| Commission’s TEDB and PDF | free | all rates | $0 | Hand transcription, no API, no category hints |
| Veriport 2,000 pack | $9 once | 2,000 calls, 12 months | $4.50 | Prepaid, no renewal |
| Veriport 10,000 pack | $29 once | 10,000 calls, 12 months | $2.90 | Prepaid, no renewal |
| Veriport monthly plan | $15 / month | 5,000 calls + bulk | $3.00 | Subscription, cancel any time |
Free before any of this: 100 calls a month with a key, 20 a day without one. A 4xx or 5xx is never counted.
Questions
- How current is the table?
- Every row carries its own
verifiedOn; the oldest is 2026-10-24 and the next review is 2026-11-24. A change announced by an administration between reviews is applied when it takes effect and noted in the changelog. - Does it cover the UK, Switzerland or Norway?
- No: the 27 member states only. Greece is
EL. Northern Ireland follows the UK rates and is not in the table. - Can I rely on the category hints for my product?
- They state what the state applies to the usual case of the category and point at the national page; the classification of your supply is yours, and the row’s note says where the usual case splits.
- Is there a free way to get the whole table?
- Yes: the 27 country pages and the free lookup show every row;
GET /vat/ratesreturns them as JSON within the 20 anonymous calls a day.
100 calls a month: enough to refresh the 27 rows every day.
Get a free keySources
- VAT rates applied in the Member States of the European Union (situation at 1 January 2025) and the Taxes in Europe Database (TEDB) — European Commission, DG TAXUD, consulted 2026-10-24. Rates changed after the January 2025 edition (EE July 2025, RO August 2025) are taken from the national pages cited on the row and must be re-read monthly.
- Council Directive 2006/112/EC on the common system of value added tax, Articles 96–105 and Annex III (reduced rates), as amended by Directive (EU) 2022/542 — EUR-Lex, consulted 2026-10-24
- VAT e-commerce rules: One Stop Shop (OSS) and Import One Stop Shop (IOSS) — European Commission, DG TAXUD, consulted 2026-10-24