VIES VAT Number Validation — walidacja techniczna numerów VAT UE

Zespół Redakcyjny ViesVATZaktualizowano 2026-05-28

Termin „VIES VAT number validation” jest międzynarodowym standardem opisu procesu walidacji. Wyjaśniamy techniczną stronę: jakie są reguły walidacji, jak działa cyfra kontrolna i jak zaprogramować walidator.

Walidacja numeru VAT w VIES odbywa się dwuetapowo: najpierw walidacja formatu (długość, dozwolone znaki, cyfra kontrolna) lokalnie, potem zapytanie do rejestru krajowego o status aktywności. Oba etapy są niezbędne.

Dwa poziomy walidacji

Walidacja VIES składa się z dwóch poziomów: lokalna walidacja formatu i zdalna walidacja statusu. Lokalna walidacja sprawdza, czy numer ma poprawną długość dla danego kraju, czy zawiera tylko dozwolone znaki (cyfry i litery zgodnie ze specyfikacją), oraz czy spełnia regułę cyfry kontrolnej (jeśli kraj ją stosuje). Zdalna walidacja to zapytanie do rejestru krajowego: czy numer istnieje i czy jest aktywny. Każda poważna integracja musi wykonać oba poziomy — lokalna walidacja zapobiega marnowaniu zapytań do VIES na oczywiście błędne numery.

Cyfra kontrolna — algorytmy krajowe

Większość krajów UE stosuje algorytmy walidacji cyfry kontrolnej. Polska: NIP używa modulo 11 z wagami 6,5,7,2,3,4,5,6,7. Niemcy: USt-IdNr. używa algorytmu MOD 11/10. Francja: TVA = (12 + 3 × SIREN mod 97) mod 97 jako dwucyfrowy klucz. Włochy: 11-cyfrowy NIP używa Luhn-like z różnymi wagami dla pozycji parzystych i nieparzystych. Hiszpania: NIF kombinuje literę inicjalną typu firmy z 7 cyframi i literą kontrolną wyliczaną tablicowo. Pełne algorytmy w naszej dokumentacji [VIES SOAP API](/poradniki/vies-soap-api/).

Pułapki implementacyjne

Najczęstsze błędy programistyczne: (1) nieusunięcie spacji, myślników i kropek z wejścia przed walidacją; (2) brak rozróżnienia wielkości liter (HR, ES, IE używają liter — zawsze normalizuj do uppercase); (3) zakładanie, że VAT zawsze zaczyna się od kodu kraju (część krajów akceptuje wpis bez prefiksu); (4) zbyt rygorystyczna walidacja długości dla krajów o zmiennej długości (RO 2–10 cyfr, CZ 8–10 cyfr); (5) ignorowanie kodu MS_UNAVAILABLE jako błędu permanentnego (to chwilowa awaria, należy retry).

Walidacja SOAP — oficjalna metoda KE

Oficjalne API KE to SOAP service pod adresem ec.europa.eu/taxation_customs/vies/services/checkVatService. Metoda checkVat przyjmuje countryCode (string, 2 znaki ISO) i vatNumber (string), zwraca strukturę z polami: valid (boolean), name (string), address (string), requestDate (xsd:date). Metoda kwalifikowana checkVatApprox dodatkowo przyjmuje requesterMemberStateCode i requesterNumber, i w odpowiedzi zawiera requestIdentifier — czyli nasz identyfikator konsultacji. Większość integracji powinna używać checkVatApprox, by uzyskać dowód weryfikacji.

Walidacja REST — nowsza alternatywa

KE udostępnia od 2020 endpoint REST: ec.europa.eu/taxation_customs/vies/rest-api/check-vat-test-service. Przyjmuje JSON: {"countryCode":"DE","vatNumber":"123456789","requesterMemberStateCode":"PL","requesterNumber":"1234567890"}. Zwraca strukturę zbliżoną do SOAP, ale w JSON. REST jest łatwiejszy w integracji z nowoczesnymi językami i frameworkami, ale rzadziej jest aktualizowany. Dla produkcji nadal rekomendujemy SOAP — jest stabilniejszy i lepiej udokumentowany.

Kody błędów i ich znaczenie

INVALID_INPUT — błędny format numeru lub kodu kraju. INVALID_REQUESTER_INFO — błędne dane requestera (przy zapytaniu kwalifikowanym). SERVICE_UNAVAILABLE — sam VIES KE jest niedostępny (rzadko, zwykle <1h/rok). MS_UNAVAILABLE — rejestr konkretnego kraju jest niedostępny (częściej, zwykle 15–60 minut). TIMEOUT — przekroczony czas oczekiwania (powyżej 30s). VAT_BLOCKED — numer jest zablokowany w bazie (rzadko, oznacza nadużycie). Każdy kod wymaga innej strategii retry — szczegóły w naszej dokumentacji.

Best practices integracji

Buforuj wyniki VIES przez 24 godziny — to oficjalne zalecenie KE, by nie obciążać systemu. Implementuj exponential backoff dla błędów MS_UNAVAILABLE (1 min, 5 min, 15 min, 1 godz.). Loguj każde zapytanie razem z identyfikatorem konsultacji do bazy własnej — to Twój dowód w razie kontroli. Asynchronizuj walidację — nie blokuj UI czekając na odpowiedź VIES, użyj kolejki zadań. I najważniejsze: nigdy nie wystawiaj faktury WDT bez świeżej weryfikacji (poniżej 24h).

Implementacja walidacji NIP w PHP — pełny kod

Walidacja polskiego NIP w PHP wymaga ok. 15 linii kodu. function validatePolishNip($nip): bool { if (!preg_match('/^[0-9]{10}$/', $nip)) return false; $weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]; $sum = 0; for ($i = 0; $i < 9; $i++) { $sum += intval($nip[$i]) * $weights[$i]; } $checksum = $sum % 11; if ($checksum === 10) return false; return $checksum === intval($nip[9]); }. Użycie: validatePolishNip('5252023338') — zwraca true albo false. Walidacja niemieckiego USt-IdNr. jest bardziej skomplikowana: stosuje wariant ISO 7064 MOD 11/10. Walidacja francuskiego TVA: function validateFrenchTva($num): bool { if (!preg_match('/^[0-9A-Z]{2}[0-9]{9}$/', $num)) return false; $siren = intval(substr($num, 2)); $key = (12 + 3 * ($siren % 97)) % 97; $expectedKey = sprintf('%02d', $key); return substr($num, 0, 2) === $expectedKey; }. Wszystkie te algorytmy są w naszym SDK PHP — wywołanie ViesValidator::validateFormat('PL', '5252023338') uruchamia odpowiednią walidację per kraj, bez konieczności pisania kodu samodzielnie.

Walidacja w Python z biblioteką stdlib

Python umożliwia czystą implementację walidacji bez zależności zewnętrznych. def validate_polish_nip(nip: str) -> bool: if not nip.isdigit() or len(nip) != 10: return False; weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]; total = sum(int(nip[i]) * weights[i] for i in range(9)); checksum = total % 11; return checksum != 10 and checksum == int(nip[9]). Dla bardziej zaawansowanych zastosowań (asynchroniczne weryfikacje, integracja z aiohttp/Django/FastAPI) zalecamy nasz SDK Python: pip install viesvat. Użycie: from viesvat import ViesClient; client = ViesClient(api_key='svies_pk_...'); result = await client.check('DE', '123456789'); print(result.valid, result.consultation_number). SDK obsługuje async/await, automatyczne retry MS_UNAVAILABLE, cache 24h, integrację z popularnymi loggerami (structlog, loguru). Dla integracji z Django: from viesvat.django import ViesField — pole modelu, które automatycznie waliduje VAT przed zapisem rekordu. Dla FastAPI: dependency injection ViesClient — wstrzyknięcie klienta do endpointów.

Walidacja w Node.js / TypeScript

Środowisko Node.js i TypeScript ma rosnący udział w polskich integracjach VIES — zwłaszcza w e-commerce, headless CMS i nowoczesnych ERP zbudowanych jako mikrousługi. Walidacja NIP w czystym TypeScript: function validatePolishNip(nip: string): boolean { if (!/^[0-9]{10}$/.test(nip)) return false; const weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]; const sum = weights.reduce((s, w, i) => s + parseInt(nip[i]) * w, 0); const checksum = sum % 11; return checksum !== 10 && checksum === parseInt(nip[9]); }. Dla integracji produkcyjnej zalecamy nasz SDK Node.js: npm install @viesvat/sdk. Pełne typy TypeScript: import { ViesClient, VatCheckResult } from '@viesvat/sdk'; const client = new ViesClient({ apiKey: process.env.SVIES_KEY }); const result: VatCheckResult = await client.check({ country: 'DE', vat: '123456789' }); if (result.valid) console.log(result.consultationNumber);. SDK ma wbudowane wsparcie dla popularnych frameworków: Express middleware, NestJS provider, Next.js API routes helper, tRPC procedure. Dla aplikacji edge (Cloudflare Workers, Vercel Edge Functions) udostępniamy lekką wersję bez zależności na Node-specific API.

Walidacja w .NET — C# i F#

.NET pozostaje dominującym środowiskiem dla większych polskich firm korzystających z SAP, Microsoft Dynamics i Comarch ERP. Walidacja NIP w C#: public static bool ValidatePolishNip(string nip) { if (string.IsNullOrEmpty(nip) || nip.Length != 10 || !nip.All(char.IsDigit)) return false; int[] weights = { 6, 5, 7, 2, 3, 4, 5, 6, 7 }; int sum = 0; for (int i = 0; i < 9; i++) sum += (nip[i] - '0') * weights[i]; int checksum = sum % 11; return checksum != 10 && checksum == (nip[9] - '0'); }. Dla integracji produkcyjnej: NuGet ViesVAT.Sdk, wspierane .NET 6/7/8/9. Użycie: var client = new ViesClient(apiKey); var result = await client.CheckAsync('DE', '123456789'); if (result.IsValid) Console.WriteLine(result.ConsultationNumber);. SDK ma wbudowane wsparcie dla Entity Framework (atrybut [ViesValidated] na property modelu), ASP.NET Core (dependency injection), Polly (dla custom retry policies), Serilog (structured logging). Dla F# udostępniamy idiomatyczne API z Result<T, ViesError> — funkcyjne podejście do błędów zamiast wyjątków.

Walidacja w Javie — Spring Boot, Jakarta EE

Java pozostaje wybraną platformą dla dużych polskich firm z monolitycznym ERP lub mikrousługową architekturą Spring Boot. Walidacja NIP w czystej Javie: public static boolean validatePolishNip(String nip) { if (nip == null || !nip.matches(\"[0-9]{10}\")) return false; int[] weights = {6, 5, 7, 2, 3, 4, 5, 6, 7}; int sum = 0; for (int i = 0; i < 9; i++) sum += Character.getNumericValue(nip.charAt(i)) * weights[i]; int checksum = sum % 11; return checksum != 10 && checksum == Character.getNumericValue(nip.charAt(9)); }. Dla produkcji: Maven Central pl.viesvat:sdk, wspierane Java 11+. Użycie: ViesClient client = ViesClient.builder().apiKey(\"svies_pk_...\").build(); VatCheckResult result = client.check(\"DE\", \"123456789\"); if (result.isValid()) System.out.println(result.getConsultationNumber());. Adnotacje dla Spring Boot: @ValidVAT na polach DTO, automatyczna walidacja w controller'ze z odpowiedzią 400 dla nieaktywnych. Integracja z popularnymi bibliotekami: Lombok, MapStruct, Hibernate (Validator extensions), Resilience4j (retry/circuit breaker), Micrometer (metryki). Dla Jakarta EE / Quarkus / Micronaut udostępniamy adaptery zgodne z CDI.

Walidacja niemieckiego USt-IdNr. — algorytm szczegółowy

Niemiecki algorytm dla USt-IdNr. opiera się na ISO 7064 MOD 11/10, ale z niemiecką specyfiką. Krok 1: weź pierwsze 8 cyfr (z 9 całkowitych). Krok 2: zainicjalizuj wartość pomocniczą P = 10. Krok 3: dla każdej z pierwszych 8 cyfr: M = (cyfra + P) mod 10; jeśli M = 0, ustaw M = 10; P = (2 × M) mod 11. Krok 4: oblicz końcową cyfrę kontrolną: jeśli (11 - P) = 10, wynik = 0; w przeciwnym razie wynik = 11 - P. Krok 5: porównaj z 9. cyfrą USt-IdNr. Implementacja w PHP zajmuje ok. 12 linii. Funkcja: function validateGermanUstId(string $vat): bool { if (!preg_match('/^[0-9]{9}$/', $vat)) return false; $p = 10; for ($i = 0; $i < 8; $i++) { $m = (intval($vat[$i]) + $p) % 10; if ($m === 0) $m = 10; $p = (2 * $m) % 11; } $check = (11 - $p) === 10 ? 0 : (11 - $p); return $check === intval($vat[8]); }. Algorytm wydaje się skomplikowany, ale skutecznie wykrywa większość literówek. W praktyce ok. 97% błędnie wpisanych numerów odrzuca się offline bez zapytania do VIES.

Praktyczne narzędzia online — walidatory bez API

Dla pojedynczych weryfikacji formatu (bez sprawdzania w VIES) istnieją darmowe walidatory online. ViesVAT udostępnia bezpłatny walidator formatu pod /walidator-vat — wpisujesz numer, wybierasz kraj, walidator sprawdza format i cyfrę kontrolną lokalnie w przeglądarce (bez wysyłania do serwera). To przydatne dla księgowych, którzy chcą szybko sprawdzić, czy numer w mailu od kontrahenta wygląda poprawnie, zanim wprowadzą go do programu księgowego. Inne darmowe walidatory: VIES-Validator (Niemcy, niemiecki interfejs), vatify.eu (międzynarodowy), check-vat.eu (angielski). Wszystkie te narzędzia robią to samo — walidują format offline. Różnice w UX i językach. Dla deweloperów: większość bibliotek open source ma wbudowane walidatory wszystkich krajów UE (vies-php, vatchecker, vat-validator-js). Można je używać niezależnie od własnej integracji VIES.

Testowanie integracji — narzędzia QA

Profesjonalne testowanie integracji VIES wymaga deterministycznych narzędzi. Sandbox ViesVAT (/api/v1/sandbox/check): deterministyczne odpowiedzi dla testowych numerów (DE123456789 = aktywny, DE000000000 = nieaktywny, DE999999999 = MS_UNAVAILABLE). Wymaga klucza Free, nie konsumuje quoty VIES. WireMock dla SOAP: lokalny mock VIES dla testów unit/integration w CI/CD. Konfigurowalne odpowiedzi per requestId. Postman/Insomnia collection: gotowy zestaw zapytań SOAP i REST do VIES, dostępny w naszym repo GitHub. Newman dla CI: uruchom Postman collection w GitHub Actions/GitLab CI. Mountebank: bardziej zaawansowany mock z fizyczną kontrolą czasu odpowiedzi (symuluj wolne rejestry IT). Chaos engineering: symulowanie awarii MS_UNAVAILABLE losowo w testach e2e (5% zapytań). Te narzędzia razem dają pełne pokrycie scenariuszy. ViesVAT jako klient KE ma własną platformę testową (200+ scenariuszy regresyjnych) — udostępniamy ją klientom Plan Business jako shared environment.

Walidacja jako pierwsza linia obrony — strategia warstwowa

Skuteczna walidacja numerów VAT używa wielowarstwowej strategii — od najszybszego/najtańszego do najwolniejszego/najdroższego. Warstwa 1: walidacja formatu regex. Najszybsza (mikrosekundy), wymaga tylko regex per kraj. Odrzuca ~30% błędów wejściowych. Warstwa 2: walidacja cyfry kontrolnej. Szybka (milisekundy), wymaga implementacji algorytmu per kraj. Odrzuca dodatkowe ~40% błędów (literówki). Warstwa 3: cache lokalny ostatnich weryfikacji. Szybki (~10ms), wymaga bazy danych z TTL 24h. Eliminuje powtarzające się zapytania. Warstwa 4: zapytanie do API VIES. Wolniejsze (1–3 sekundy), ale autoritarna weryfikacja statusu. Pełna informacja. Warstwa 5: weryfikacja w lokalnym rejestrze handlowym (KRS, Handelsregister). Wolna (sekundy do minut), wymaga osobnej integracji. Tylko dla największych transakcji. Implementacja w PHP: function validateVatComprehensive(string $country, string $vat): array { if (!validateFormat($country, $vat)) return ['valid' => false, 'layer' => 'format']; if (!validateChecksum($country, $vat)) return ['valid' => false, 'layer' => 'checksum']; if ($cached = getCache($country, $vat)) return $cached; $viesResult = checkVies($country, $vat); setCache($country, $vat, $viesResult); return $viesResult; }. Strategia warstwowa redukuje koszty (mniej zapytań VIES) i przyspiesza odpowiedzi (większość weryfikacji kończy się na warstwie 1-3).

Edge cases — nietypowe sytuacje walidacji

Praktyka pokazuje kilka nietypowych sytuacji, które wymagają specjalnej obsługi w walidacji. Sytuacja 1: leading zeros w numerze. Niektóre kraje (Belgia, Holandia) wymagają pełnej liczby cyfr z leading zerami. Inne (Niemcy) nie zezwalają na leading zera. Walidator musi normalizować zgodnie z konwencją kraju. Sytuacja 2: prefiks kraju w numerze VAT vs jako osobny parametr. Klienci często wysyłają numer z prefiksem (np. DE123456789) lub bez (123456789 + kraj DE). Walidator musi obsługiwać oba i normalizować. Sytuacja 3: bytea, BOM, znaki niewidoczne. Numery skopiowane z PDF/Word mogą zawierać niewidoczne znaki (zero-width space, BOM, soft hyphen). Walidator musi je usunąć przed walidacją. Sytuacja 4: znaki cyrylica/grecka wyglądające jak ASCII. Greckie A (Α) wygląda jak łacińskie A, ale to inny kod Unicode. Walidator musi odrzucić — VIES akceptuje tylko ASCII. Sytuacja 5: duże spacje (em space, no-break space). Powinny być usunięte. Sytuacja 6: full-width digits (012) z systemów japońskich. Konwertuj na half-width. Te wszystkie edge cases są obsłużone w naszych SDK — nie musisz ich implementować samodzielnie.

Najczęściej zadawane pytania

Czy mogę walidować numer VAT offline?

Częściowo — można sprawdzić format i cyfrę kontrolną lokalnie, ale aktywność tylko przez VIES.

Który protokół wybrać — SOAP czy REST?

SOAP dla produkcji (stabilność), REST dla prototypów i nowych integracji.

Jaki jest limit zapytań?

KE nie publikuje oficjalnego limitu, ale rekomenduje <10 zapytań/sekundę. Nasze API kolejkuje powyżej tego progu.

Czy istnieje sandbox?

Nie oficjalnie — nasze API udostępnia własny sandbox z testowymi numerami.

Jak walidować cyfrę kontrolną polskiego NIP?

NIP × wagi (6,5,7,2,3,4,5,6,7) mod 11 = ostatnia cyfra NIP. Jeśli wynik = 10, NIP nieprawidłowy.

Czy SOAP/REST są darmowe?

Tak, oba są publicznie dostępne i darmowe.

Powiązane