VIES

VIES (VAT Validation)

A Dart client for the EU VIES checkVat SOAP service. Validates a VAT number and, when valid, returns the registered business information (legal name, address) published by the member state.

Pub Version Maintainer License Maintenance Null Safety Platforms

Install

dart pub add vies      # Dart
flutter pub add vies   # Flutter

Usage

import 'package:vies/vies.dart';

Future<void> main() async {
  try {
    final response = await ViesProvider.validateVat(
      vatNumber: 'BE1000341796',
      timeout: const Duration(seconds: 15),
      retries: 2, // transient VIES faults are retried with backoff
    );
    print('Valid VAT for ${response.name}');
    print('Address: ${response.address}');
  } on ViesClientError catch (e) {
    // Invalid VAT, parsing errors, INVALID_INPUT faults ...
    print('Client error: ${e.code.wireName} — ${e.message}');
  } on ViesServerError catch (e) {
    // Network, timeout, VIES outage ...
    print('Server error: ${e.code.wireName} — ${e.message}');
  }
}

Validation levels

// Offline regex check only (no network call):
await ViesProvider.validateVat(
  vatNumber: 'FR64443061841',
  validationLevel: ValidationLevel.regex,
);

// Skip the regex pre-check, hit VIES directly:
await ViesProvider.validateVat(
  vatNumber: 'FR64443061841',
  validationLevel: ValidationLevel.vies,
);

// Default: regex first, then VIES (saves a network round-trip on bad input):
await ViesProvider.validateVat(
  vatNumber: 'FR64443061841',
  // validationLevel: ValidationLevel.all,
);

A regex-only result is not a validation. source says which check answered, so a shape check is never recorded as a confirmation from a member state:

final response = await ViesProvider.validateVat(
  vatNumber: 'FR64443061841',
  validationLevel: ValidationLevel.regex,
);
response.valid;  // true
response.source; // ValidationSource.regex, nobody confirmed anything

Offline shape check

VatShape answers without a network call. RegexType.eu applies the format published by each member state, RegexType.world a single permissive shape:

VatShape.isValid('NL123456789b01', RegexType.eu); // true, case-insensitive
VatShape.isValid('RO99908', RegexType.eu);        // true, RO is 2 to 10 digits
VatShape.isValid('DE12345', RegexType.eu);        // false, DE is 9 digits

VatShape.normalize(' 1234 5678-9b01 '); // 123456789B01
VatShape.supportedCountryCodes;         // the prefixes RegexType.eu knows

Reusing an HTTP client

For batch validation, inject a shared http.Client to enable connection pooling:

final client = http.Client();
try {
  for (final vat in batch) {
    await ViesProvider.validateVat(
      vatNumber: '${vat.country}${vat.number}',
      client: client,
    );
  }
} finally {
  client.close();
}

timeout bounds one attempt, not the whole call. With retries: 2 a call can take up to three times the timeout plus the backoff, which is capped at maxRetryBackoff.

Test endpoint

serviceUrl selects the endpoint. The VIES test service answers deterministically from the VAT number tail, which is useful in integration tests that must not touch the real database:

await ViesProvider.validateVat(
  vatNumber: 'BE100',
  serviceUrl: viesTestServiceUrl,
);

Country prefixes

VIES uses ISO 3166-1 alpha-2 codes, with two exceptions:

  • EL for Greece (instead of GR).
  • XI for Northern Ireland (post-Brexit).

vatNumber takes the prefix and the number together, with or without separators. Spaces, dots and hyphens are stripped before anything else.

await ViesProvider.validateVat(vatNumber: 'BE1003546213');
await ViesProvider.validateVat(vatNumber: 'BE1003.546.213');
await ViesProvider.validateVat(vatNumber: 'be 1003 546 213');

Only a prefix VIES knows counts as one, so a Spanish B12345678 is left whole. With no country to read, the call throws ViesClientError carrying ViesErrorCode.invalidInput.

countryCode is deprecated and goes away in 3.0.0. Keep the prefix in the number: a French key of two letters can read as a country code, so countryCode: 'FR' with vatNumber: 'BE123456789' reaches Belgium, where vatNumber: 'FRBE123456789' reaches France.

VatShape.split does the same reading offline, and returns null when there is no prefix to read.

Error codes

ViesErrorCode value wireName Type When
invalidInput INVALID_INPUT client Country code unknown / VAT number empty
invalidVatNumber INVALID_VAT_NUMBER client Failed regex or VIES valid=false
parsingError PARSING_ERROR client Could not parse the SOAP body
soapFault SOAP_FAULT client Other SOAP fault
invalidRequesterInfo INVALID_REQUESTER_INFO client VIES rejected the requester block
timeout TIMEOUT server Request timed out (retryable)
socketException SOCKET_EXCEPTION server No internet connection
serviceUnavailable SERVICE_UNAVAILABLE server VIES is down (retryable)
msUnavailable MS_UNAVAILABLE server Member-state DB is down (retryable)
msMaxConcurrentReq MS_MAX_CONCURRENT_REQ server Member-state DB rate-limited (retryable)
serverBusy SERVER_BUSY server VIES is overloaded (retryable)
serverDisconnected SERVER_DISCONNECTED server Generic transport failure
unknown UNKNOWN server Anything else

Dependencies

License

MIT - see LICENSE.

Libraries

vies
Dart client for the VIES (VAT Information Exchange System) SOAP service.