# EU VAT Rates API: all 27 countries (TEDB) (`clearsource/eu-vat-rates`) Actor

Standard and reduced VAT rates for all 27 EU Member States from the European Commission's Taxes in Europe Database (TEDB): by date, category or CN code, as clean JSON with source and retrieval time. Information only, not tax advice. Not affiliated with the European Commission.

- **URL**: https://apify.com/clearsource/eu-vat-rates.md
- **Developed by:** [PPFTEC S.R.L](https://apify.com/clearsource) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EU VAT rates (TEDB)

> Operator: PPFTEC S.R.L. (https://ppftec.com). Source allow-listed with conditions (Petre, 2026-10-10). Information only; not legal or tax advice.

Get the **VAT rates of the 27 EU Member States** as JSON, from the European Commission's **Taxes in Europe Database (TEDB)** web service (`VatRetrievalService`, anonymous SOAP). Ask for today or any date, for all countries or a few, optionally narrowed to CN codes, CPA codes or TEDB categories. Every record carries its source, retrieval time, licence attribution and a disclaimer.

**For:** e-commerce and invoicing tools, OSS/IOSS reporting, ERP and billing integrations, tax-content teams and AI agents that need EU VAT rates as data.

**Information only; not legal or tax advice.** Rates are as reported by Member States to TEDB; verify against national law before charging VAT. This actor never computes tax amounts or decides whether an exemption applies.

### Quick start

Standard and reduced rates today for Romania, Germany and France (one record per country):

```json input
{ "countries": ["RO", "DE", "FR"] }
```

All 27 Member States on a given date:

```json input
{ "situationOn": "2026-01-01" }
```

Every rate rule for Greece (TEDB code `EL`; `GR` is accepted too), with categories, CN/CPA codes and comments:

```json input
{ "countries": ["GR"], "outputMode": "detailed" }
```

How the rate for milk (CN 0401) changed in Romania and Germany since January 2025:

```json input
{
  "countries": ["RO", "DE"],
  "cnCodes": ["0401"],
  "dateFrom": "2025-01-01",
  "dateTo": "2026-10-10",
  "outputMode": "detailed"
}
```

Validate an input without calling TEDB (no request, nothing emitted, nothing charged; the SOAP requests are stored under key `DRY_RUN`):

```json input
{ "countries": ["RO"], "categories": ["FOODSTUFFS"], "dryRun": true }
```

### Output examples

From the TEDB response recorded on 2026-10-10 (`test/fixtures/all27-2026-10-10.xml`), replayed offline. Every record validates against `src/schemas/` before it is pushed; a record that fails is a bug and fails the run.

**Summary** (`outputMode: "summary"`, the default), Romania:

```json output
{
  "record_type": "vat-rate-summary",
  "country": "RO",
  "country_iso3166": "RO",
  "situation_on": "2026-10-10",
  "standard_rate": 21,
  "other_standard_rates": [],
  "reduced_rates": [
    {
      "rate_value": 11,
      "rate_kind": "REDUCED_RATE",
      "record_count": 33,
      "categories": [
        "ACCOMMODATION",
        "AGRICULTURAL_PRODUCTION",
        "CULTURAL_EVENTS",
        "FOODSTUFFS",
        "HOUSING_PROVISION",
        "LOAN_LIBRARIES",
        "PHARMACEUTICAL_PRODUCTS",
        "RESTAURANT",
        "SUPPLY_WATER",
        "SUSTAINABLE_ENERGY",
        "WOOD_ARTICLE98"
      ]
    }
  ],
  "other_treatments": [{ "rate_kind": "EXEMPTED", "record_count": 1 }],
  "rate_record_count": 35,
  "requested": { "situation_on": "2026-10-10", "date_from": null, "date_to": null, "cn_codes": [], "cpa_codes": [], "categories": [] },
  "disclaimer": "Information only; not legal or tax advice. Rates as reported by Member States to TEDB; verify against national law before charging VAT. Not affiliated with the European Commission.",
  "provenance": {
    "envelope_version": "1.0.0",
    "source_id": "tedb-vat-retrieval",
    "source_url": "https://ec.europa.eu/taxation_customs/tedb/ws/",
    "source_request": {
      "method": "POST",
      "body_sha256": "0f33827ec05304381d492814eef0f31bd4d3f02d7140918236d3ff4f5a89668c",
      "page": "1/1"
    },
    "retrieved_at": "2026-10-10T11:22:29.007Z",
    "source_version": {
      "api_version": "v1",
      "doc_sha256": "6ed97fbe85757712c4c3660e9f6c7b929553e2d69a0890ec5f926a60c9cce32f",
      "dataset_revision": "situationOn=2026-10-10"
    },
    "producer": { "name": "eu-vat-rates", "version": "0.1.0", "build": null, "run_id": null, "adapter": "tedb-soap-v1" },
    "record_hash": "sha256:e805f76a394815ecc52a93b15d32442a58f2b2ca6d8c12b92bff189cafd026fe",
    "licence": {
      "id": "CC-BY-4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/",
      "attribution": "Source: European Commission, Taxes in Europe Database (TEDB), https://ec.europa.eu/taxation_customs/tedb/ — data supplied by Member States; retrieved 2026-10-10; modified by Clearsource (normalised)"
    },
    "personal_data": "none"
  }
}
```

**Detailed** (`outputMode: "detailed"`), one of the 35 Romanian results:

```json output
{
  "record_type": "vat-rate",
  "country": "RO",
  "country_iso3166": "RO",
  "rate_type": "REDUCED",
  "rate_value": 11,
  "rate_kind": "REDUCED_RATE",
  "situation_on": "2026-07-01",
  "source_situation_on": "2026-07-01+02:00",
  "category": { "id": "WOOD_ARTICLE98", "description": "Wood used as firewood (until 1 January 2030)" },
  "cn_codes": [
    { "code": "4401 41 00", "description": "Sawdust" },
    { "code": "4401 49 00", "description": "Other" }
  ],
  "cpa_codes": [],
  "comment": "The supply for the use as heating fuel of sawdust, waste and non-agglomerated wood scraps, carried out to final beneficiaries, on the basis of a statement of liability made available to the supplier. See art. 291 para. (2) letters i), j) of the Fiscal Code.",
  "requested": { "situation_on": "2026-10-10", "date_from": null, "date_to": null, "cn_codes": [], "cpa_codes": [], "categories": [] },
  "disclaimer": "Information only; not legal or tax advice. Rates as reported by Member States to TEDB; verify against national law before charging VAT. Not affiliated with the European Commission.",
  "provenance": {
    "envelope_version": "1.0.0",
    "source_id": "tedb-vat-retrieval",
    "source_url": "https://ec.europa.eu/taxation_customs/tedb/ws/",
    "source_request": {
      "method": "POST",
      "body_sha256": "0f33827ec05304381d492814eef0f31bd4d3f02d7140918236d3ff4f5a89668c",
      "page": "1/1"
    },
    "retrieved_at": "2026-10-10T11:22:35.939Z",
    "source_version": {
      "api_version": "v1",
      "doc_sha256": "6ed97fbe85757712c4c3660e9f6c7b929553e2d69a0890ec5f926a60c9cce32f",
      "dataset_revision": "situationOn=2026-10-10"
    },
    "producer": { "name": "eu-vat-rates", "version": "0.1.0", "build": null, "run_id": null, "adapter": "tedb-soap-v1" },
    "record_hash": "sha256:e2366776a8ad65a06f19057ecabf7c1bc39866c20934d337e961bddd1b673d49",
    "licence": {
      "id": "CC-BY-4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/",
      "attribution": "Source: European Commission, Taxes in Europe Database (TEDB), https://ec.europa.eu/taxation_customs/tedb/ — data supplied by Member States; retrieved 2026-10-10; modified by Clearsource (normalised)"
    },
    "personal_data": "none"
  }
}
```

### Fields

**Both modes**

| Field | Notes |
| --- | --- |
| `record_type` | `vat-rate-summary` or `vat-rate`. |
| `country` | TEDB Member State code (`EL` for Greece). |
| `country_iso3166` | ISO 3166-1 alpha-2 (`GR` for Greece). |
| `requested` | The query the record answers: `situation_on` or `date_from`/`date_to`, and your CN/CPA/category filters. |
| `disclaimer` | Same text on every record (see Disclaimer). |
| `provenance` | Source URL, SHA-256 of the SOAP request body (`source_request.body_sha256`), retrieval time, TEDB query date (`source_version.dataset_revision`), hash of the TEDB type XSD we built against (`doc_sha256`), record hash, licence with attribution. See `docs/spec/provenance-schema.md`. |

**Summary** (one per Member State, charged once per country returned)

| Field | Notes |
| --- | --- |
| `situation_on` | The date you asked for. |
| `standard_rate` | The general standard rate: the `STANDARD`/`DEFAULT` result without comment, category or codes. |
| `other_standard_rates` | Other `STANDARD` results with their TEDB comment, e.g. Spain 7 % "Canary Islands", Germany 19 % "Import". |
| `reduced_rates` | Distinct reduced, super-reduced and parking rates, highest first, with `record_count` and TEDB `categories`. |
| `other_treatments` | Counts of `EXEMPTED`, `OUT_OF_SCOPE`, `NOT_APPLICABLE` results. |
| `rate_record_count` | Number of TEDB results behind the summary (get them with `outputMode: "detailed"`). |

**Detailed** (one per TEDB `vatRateResults` element, charged per record)

| Field | From TEDB | Notes |
| --- | --- | --- |
| `rate_type` | `type` | `STANDARD` or `REDUCED`. |
| `rate_value` | `rate/value` | Percentage as a number; `null` when TEDB gives none. |
| `rate_kind` | `rate/type` | `DEFAULT`, `REDUCED_RATE`, `SUPER_REDUCED_RATE`, `PARKING_RATE`, `EXEMPTED`, `OUT_OF_SCOPE`, `NOT_APPLICABLE`. |
| `situation_on` | `situationOn` | Date of the situation TEDB reports (when that rule started), `YYYY-MM-DD`; verbatim value in `source_situation_on`. |
| `category` | `category` | `{ id, description }` or `null`. |
| `cn_codes`, `cpa_codes` | `cnCodes`, `cpaCodes` | `[{ code, description }]`, as TEDB prints them (e.g. `0504 00 00`). |
| `comment` | `comment` | Free text supplied by the Member State, often with the legal reference; `null` when absent. |

### Pricing (pay per event)

Example: all 27 countries in summary mode ≈ $0.054 per run; all 27 in detailed mode (about 1,100 records) ≈ $0.22.

| Event | Price | When |
| --- | --- | --- |
| `apify-actor-start` | $0.00005 (Apify default) | per run |
| `country-result` | $0.002 | per country record pushed in summary mode |
| `rate-record` | $0.0002 | per record pushed in detailed mode |

Nothing is charged for dry runs, health runs, invalid input, TEDB errors, schema drift or countries without results. Set a maximum charge per run in Apify; the actor stops cleanly when it is reached. `maxResults` (default 10,000) caps the records per run.

### Limits

- **Requests:** at most 9 countries per SOAP request (all 27 = 3 requests), one request at a time, at least 1 s apart. A full 27-country response was 1.5 MB and took 8.9 s on 2026-10-10.
- **Retries:** HTTP 429 and 5xx (including SOAP `Server` faults) are retried with backoff (1, 2, 4, 8 s; `Retry-After` honoured; 5 attempts), then the run fails. SOAP `Client` faults (TEDB returns them as HTTP 500) are request errors and are not retried.
- **Dates:** `situationOn` (default: today, UTC) or a `dateFrom`/`dateTo` range of at most 3,660 days, between 2000-01-01 and today + 2 years. A range needs `outputMode: "detailed"`. How far back TEDB holds data is not documented (unverified).
- **Countries:** the 27 EU Member States only. Northern Ireland (`XI`) is mentioned in the 2021 TEDB documentation but was not tested, so it is not accepted yet.
- **Filters:** up to 100 CN codes (2 to 10 digits), 100 CPA codes, 100 TEDB category identifiers. TEDB decides how a filter matches (e.g. CN `0401` also returned chapter-level and other rules in the recorded response); read `cn_codes` and `comment` before relying on a rate.
- **Run time:** at most 30 minutes per run.
- **No caching:** every run calls TEDB; schedule runs rather than polling.

### Data source and licence

Source: **European Commission, DG TAXUD — Taxes in Europe Database (TEDB)**, VatRetrievalService web service (WSDL https://ec.europa.eu/taxation_customs/tedb/ws/VatRetrievalService.wsdl, endpoint `https://ec.europa.eu/taxation_customs/tedb/ws/`, no authentication). The data is supplied to TEDB by the Member States. Copies of the WSDL and XSDs we built against (downloaded 2026-10-10) are in `src/schemas/tedb/`.

Attribution (also in every record's `provenance.licence.attribution` and in the dataset description):

> Source: European Commission, Taxes in Europe Database (TEDB), https://ec.europa.eu/taxation_customs/tedb/ — data supplied by Member States; retrieved \<date>; modified by Clearsource (normalised)

Reuse: Commission content is reused under the Commission legal notice (https://commission.europa.eu/legal-notice_en), which licenses it under **CC BY 4.0** (https://creativecommons.org/licenses/by/4.0/) and implements Commission Decision 2011/833/EU. **Modifications:** this actor converts the SOAP/XML response to JSON, renames fields, turns rate values into numbers, cuts dates to `YYYY-MM-DD` (the verbatim value is kept), maps `EL` to ISO `GR` in `country_iso3166` and, in summary mode, aggregates results per country. A TEDB-specific disclaimer page was not found (the TEDB site is a JavaScript app); that point is open.

This actor is a product of PPFTEC S.R.L. It is **not affiliated with or endorsed by the European Commission** or any Member State. It does not use EU or Commission logos.

### Reliability

- **Schema drift fails loudly.** Every TEDB response is parsed and checked against a pinned shape (`src/source-shape.schema.json`, derived from `VatRetrievalServiceType.xsd`). An unknown element, a missing element, an unknown rate type, a non-numeric rate, results for a country we did not ask for, or TEDB rejecting our request as XSD-invalid stops the run with exit code 1, the status `SCHEMA DRIFT at tedb-vat-retrieval: …` and a `DRIFT_REPORT` (expected, actual, sample) in the run's key-value store. Records already pushed stay valid.
- **Health checks.** `mode: "health"` (hidden, maintainer only) runs a canary: one SOAP call for Romania today (shape, standard rate present, output schema) and the SHA-256 of the WSDL and both XSDs against the pinned copies (a changed type XSD fails, the others warn). It pushes a record to the named dataset `pda-health` with p50/p95 latency. 4 requests, at least 1 s apart. Planned schedule: daily.
- **Kill switch.** `PDA_KILL_SWITCH=on` (or `PDA_DISABLED_ADAPTERS=tedb-soap-v1`) stops every run before any request (status `Disabled by maintainer: …`); it is checked again before every request.
- **Changelog:** see `CHANGELOG.md`.

### Security and privacy

- **Network:** the actor calls only `https://ec.europa.eu/taxation_customs/tedb/ws/` (fixed URLs; user input only reaches the XML body, escaped and validated first). Redirects are never followed.
- **Personal data:** none. Records hold rates, categories, CN/CPA codes and Member State comments about the law.
- **Container:** `apify/actor-node:24` pinned by digest; runs as the non-root user `myuser`.
- **Pay-per-event guard:** on Apify the actor refuses to run without pay-per-event pricing (maintainer override `PDA_ALLOW_NO_PPE=1` for a private build only).
- **Health mode** needs `PDA_HEALTH_ENABLED=1`, the secret env var `PDA_HEALTH_TOKEN` (≥ 32 characters, Apify secret `@pdaHealthToken`) and the same value in the secret input `healthToken`, compared in constant time; if `PDA_MAINTAINER_USER_ID` is set, `APIFY_USER_ID` must match.

### Use with AI assistants (MCP)

This actor can be called by AI assistants through Apify's MCP server (a service run by Apify; this actor is a separate, independent product of PPFTEC S.R.L., not affiliated with the European Commission). Add it to an MCP client such as Claude, VS Code or Cursor:

```
{
  "mcpServers": {
    "clearsource-eu-vat-rates": {
      "url": "https://mcp.apify.com?tools=clearsource/eu-vat-rates"
    }
  }
}
```

On first use your browser asks you to sign in to Apify (OAuth); or send your token as `Authorization: Bearer <APIFY_TOKEN>`. The assistant reads the input schema, runs the actor and then fetches the dataset items. Runs are billed to your Apify account at the pay-per-event prices above.

Try: "What are the standard and reduced VAT rates in Romania, Germany and France today?" Details of the MCP server: https://docs.apify.com/platform/integrations/mcp (checked 2026-10-10).

### Terms of use

This actor is offered by PPFTEC S.R.L. under Apify's terms plus our terms of use: https://clearsource.ppftec.com/terms

### Disclaimer

Information only; not legal or tax advice. Rates as reported by Member States to TEDB; verify against national law before charging VAT. Not affiliated with the European Commission. Data is provided "as is", as returned by TEDB at `provenance.retrieved_at`; TEDB may lag behind changes in national law.

### Calling via API

```bash
curl -X POST "https://api.apify.com/v2/acts/clearsource~eu-vat-rates/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"countries":["RO","DE","FR"]}'
```

### Development

```bash
npm ci
npm run lint && npm run typecheck && npm test && npm run build
npm run check-shared                  # src/core identical in all actors
npx -y apify-cli@latest validate-schema
## Live contract test (User-Agent clearsource/eu-vat-rates@<version> (+https://clearsource.ppftec.com)):
PDA_LIVE=1 npm run test:contract      # 1 SOAP request (RO, today)
## Offline local run with the recorded fixture (ignored on Apify):
mkdir -p storage/key_value_stores/default
echo '{"countries":["RO"]}' > storage/key_value_stores/default/INPUT.json
PDA_REPLAY_FIXTURES=$PWD/test/fixtures CRAWLEE_STORAGE_DIR=./storage node dist/main.js
```

Environment variables: see `.env.example` (no secrets are needed for normal runs).

# Changelog

This Actor's version history is a separate document: https://apify.com/clearsource/eu-vat-rates/changelog.md

# Actor input Schema

## `countries` (type: `array`):

EU Member State codes, e.g. RO, DE, FR. Greece is EL in TEDB (GR is also accepted). Leave empty for all 27.

## `situationOn` (type: `string`):

Date (YYYY-MM-DD) the rates apply on. Leave empty for today (UTC). Do not combine with a date range.

## `outputMode` (type: `string`):

Summary needs a single date. Detailed also works with a date range.

## `cnCodes` (type: `array`):

Combined Nomenclature codes, 2 to 10 digits, e.g. 0401 or "0504 00 00".

## `cpaCodes` (type: `array`):

CPA codes, e.g. 01.11 or 01.61.10.

## `categories` (type: `array`):

TEDB category identifiers, e.g. FOODSTUFFS, PHARMACEUTICAL_PRODUCTS, SUPPLY_WATER.

## `dateFrom` (type: `string`):

Start of a date range (YYYY-MM-DD). Needs "Range to" and Output = detailed. Max 3,660 days.

## `dateTo` (type: `string`):

End of the date range (YYYY-MM-DD).

## `maxResults` (type: `integer`):

Maximum number of records returned (and charged) in this run.

## `dryRun` (type: `boolean`):

Build the TEDB request(s) without sending them or charging anything; stored under key DRY_RUN.

## `mode` (type: `string`):

Internal: maintainer health check. Not for normal use; requires a maintainer token. Allowed values: run, health (validated by the actor).

## `healthToken` (type: `string`):

Reserved for the maintainer's monitor.

## Actor input object example

```json
{
  "countries": [
    "RO",
    "DE",
    "FR"
  ],
  "outputMode": "summary",
  "maxResults": 10000,
  "dryRun": false,
  "mode": "run"
}
```

# Actor output Schema

## `results` (type: `string`):

One record per Member State (summary) or per TEDB rate result (detailed), each with provenance, CC BY 4.0 attribution and disclaimer.

## `runSummary` (type: `string`):

Key-value store records such as DRY_RUN or DRIFT_REPORT when present.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "countries": [
        "RO",
        "DE",
        "FR"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("clearsource/eu-vat-rates").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "countries": [
        "RO",
        "DE",
        "FR",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("clearsource/eu-vat-rates").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "countries": [
    "RO",
    "DE",
    "FR"
  ]
}' |
apify call clearsource/eu-vat-rates --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,clearsource/eu-vat-rates"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gWhDS1AE6t8iz3Dfm/builds/eLQoeZA2FkBJWsyMO/openapi.json
