Day 3 of my 21-day API challenge is done.
Yesterday I built a Password Strength Scorer. Today I built a VAT Number Validator API - validate VAT numbers for 35 countries, get tax rates, currency info and EU membership status in one call.
If you missed the previous days:
The Problem
Every e-commerce platform selling to European businesses needs VAT validation. It sounds simple but it's surprisingly painful to build:
- Every country has a different format
- Germany is
DE+ 9 digits - France is
FR+ 2 alphanumeric + 9 digits - Netherlands is
NL+ 9 digits +B+ 2 digits - And so on for 27 EU countries plus the UK, Norway, Switzerland and more
Most developers end up copying regex patterns from Stack Overflow and maintaining a messy country-by-country switch statement. I packaged all of that into a clean API.
What I Built
The VAT Number Validator API - send it a VAT number, get back full validation info.
Input:
{
"vat_number": "DE123456789"
}
Output:
{
"success": true,
"data": {
"vat_number": "DE123456789",
"country_code": "DE",
"country": "Germany",
"is_valid": true,
"format_valid": true,
"format_expected": "DE#########",
"eu_member": true,
"tax_info": {
"standard_rate": 19,
"reduced_rates": [7],
"currency": "EUR"
},
"validation_note": "Format is valid"
}
}
And for an invalid number:
{
"vat_number": "DE123",
"is_valid": false,
"validation_note": "Expected format: DE#########"
}
The 6 Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /validate |
Validate a single VAT number |
| POST | /validate/batch |
Validate up to 20 at once |
| GET | /countries |
List all 35 supported countries |
| GET | /countries/:code |
Get info for a specific country |
| POST | /format |
Clean and normalize a VAT number |
The /validate/batch endpoint is particularly useful for platforms doing bulk customer verification β validate an entire customer list in one API call instead of looping through one by one.
The Countries Database
The core of this API is a rules database with validation patterns for every supported country:
const VAT_RULES = {
DE: {
country: "Germany",
pattern: /^DE\d{9}$/,
format: "DE#########",
standard_rate: 19,
reduced_rates: [7],
currency: "EUR",
eu_member: true,
},
ZA: {
country: "South Africa",
pattern: /^ZA4\d{9}$/,
format: "ZA4#########",
standard_rate: 15,
reduced_rates: [0],
currency: "ZAR",
eu_member: false,
},
// ... 33 more countries
};
The validation is then a simple pattern match:
function validateFormat(vat) {
const normalized = vat.toUpperCase().replace(/[\s\-\.]/g, "");
const countryCode = normalized.slice(0, 2);
const rule = VAT_RULES[countryCode];
if (!rule) return { valid: false, reason: "Unknown country prefix" };
return {
valid: rule.pattern.test(normalized),
reason: rule.pattern.test(normalized) ? "Format is valid" : `Expected: ${rule.format}`,
};
}
Supported Countries
All 27 EU Members:
Austria, Belgium, Bulgaria, Cyprus, Czech Republic, Germany, Denmark, Estonia, Greece, Spain, Finland, France, Croatia, Hungary, Ireland, Italy, Lithuania, Luxembourg, Latvia, Malta, Netherlands, Poland, Portugal, Romania, Sweden, Slovenia, Slovakia
Plus 8 Non-EU Countries:
United Kingdom, Norway, Switzerland, South Africa, Australia, New Zealand, India, Singapore, Canada
Why I Included South Africa
I'm a developer from South Africa studying accounting. South African VAT runs at 15% and every local e-commerce business dealing with international customers needs this.
Most VAT APIs focus exclusively on EU countries and completely ignore the rest of the world. That's a gap worth filling.
The Batch Endpoint
The /validate/batch endpoint validates up to 20 VAT numbers at once and returns a summary:
{
"summary": {
"total": 4,
"valid": 3,
"invalid": 1,
"eu_numbers": 2,
"non_eu_numbers": 2
}
}
Useful for:
- Validating customer lists during import
- Auditing existing customer databases
- B2B onboarding flows where you collect multiple VAT numbers
Tech Stack
- Runtime: Node.js + Express
- Hosting: Railway (free tier)
- Marketplace: RapidAPI
- Dependencies: express, cors, helmet, morgan, express-rate-limit
Zero paid APIs. Zero external calls. Pure in-memory validation β which means it's fast and costs almost nothing to run.
Try It
Live on RapidAPI - search VAT Number Validator or find me at Rapid Api.
Free tier: 10 requests/month. No credit card required.
What's Next
Day 4 tomorrow - Text Readability Scorer API.
Every content platform, SEO tool and email marketing app needs to know how readable their text is. Flesch-Kincaid score, grade level, reading time, passive voice percentage - all in one call.
Follow me here on dev.to to catch every day of the challenge. πΏπ¦
21 APIs in 21 days. Built in South Africa. Sold globally.
Top comments (0)