rechtlogisch / evatr
Checks a VAT-ID using the eVatR REST-API of the German Federal Central Tax Office (Bundeszentralamt für Steuern, BZSt)
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.9
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- pestphp/pest: ^3.0
- pestphp/pest-plugin-type-coverage: ^3.5
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- roave/security-advisories: dev-latest
This package is auto-updated.
Last update: 2025-08-13 16:18:46 UTC
README
evatr
Checks a VAT-ID using the eVatR REST-API of the German Federal Central Tax Office (Bundeszentralamt für Steuern, BZSt)
Caution
This package is in early development and is not yet ready for production use. It is currently being tested and may undergo significant changes.
Important
This is an unofficial wrapper for the eVatR API. For official documentation and terms of use, please refer to the German Federal Central Tax Office (BZSt) website.
Note
This package uses the new REST-API released in July 2025. The old XML-RPC API is being discontinued and will be sunset on November 30th, 2025 (based on information from BZSt-Newsletter USTKV 01/2025 dated July 1st, 2025).
Installation
You can install the package via composer:
composer require rechtlogisch/evatr
Quick Start
Simple Validation
Validates only the VAT-ID without company data verification:
use Rechtlogisch\Evatr\Evatr; $result = (new Evatr( vatIdOwn: 'DE123456789', // Your German VAT-ID (required) vatIdForeign: 'ATU12345678', // VAT-ID to validate (required) ))->check();
or alternatively use the helper function:
$result = checkVatId(vatIdOwn: 'DE123456789', vatIdForeign: 'ATU12345678');
Qualified Confirmation
Validates VAT-ID and verifies company data:
use Rechtlogisch\Evatr\Evatr; $result = (new Evatr( vatIdOwn: 'DE123456789', // Your German VAT-ID (required) vatIdForeign: 'ATU12345678', // VAT-ID to validate (required) company: 'Musterhaus GmbH & Co KG', // Company name (required for qualified confirmation) location: 'musterort', // City (required for qualified confirmation) street: 'Musterstrasse 22', // Street address (optional) zip: '12345', // Postal code (optional) ))->check();
or alternatively use the helper function:
$result = confirmVatId( vatIdOwn: 'DE123456789', vatIdForeign: 'ATU12345678', company: 'Musterhaus GmbH & Co KG', street: 'Musterstrasse 22', zip: '12345', location: 'musterort', );
Including Raw Response
$result = (new Evatr( vatIdOwn: 'DE123456789', vatIdForeign: 'ATU12345678' ))->includeRaw()->check();
or with helper functions
$result = checkVatId( vatIdOwn: 'DE123456789', vatIdForeign: 'ATU12345678', includeRaw: true );
API Reference
Constructor
$evatr = new Evatr( vatIdOwn: string, // Your German VAT-ID (required) vatIdForeign: string, // VAT-ID to confirm (required) company: ?string, // Company name (optional, required for qualified confirmation) location: ?string, // City (optional, required for qualified confirmation) street: ?string, // Street address (optional) zip: ?string, // Postal code (optional) );
or alternatively using RequestDto:
$request = new RequestDto( vatIdOwn: 'DE123456789', vatIdForeign: 'ATU12345678', // ... other parameters ); $evatr = new Evatr($request);
Methods
check(): ResultDto
Performs the VAT-ID confirmation:
$result = $evatr->check();
includeRaw(bool $value = true): self
Includes the raw API response in the result:
$evatr->includeRaw(true)->check();
Response Object (ResultDto)
The check()
method returns a ResultDto
object with the following methods:
$result->getVatIdOwn(): string; // Own VAT-ID which was used for the request $result->getVatIdForeign(): string; // Foreign VAT-ID which was checked $result->getId(): string; // Unique ID from API, related to request $result->getHttpStatusCode(): ?int; // HTTP status code $result->getTimestamp(): ?string; // Query timestamp (ISO-8601 string) $result->getStatus(): ?Status; // Status enum $result->getMessage(): ?string; // Human-readable message based on EVATR_LANG $result->getDateFrom(): ?string; // Valid from date $result->getDateTill(): ?string; // Valid until date $result->getCompany(): ?QualifiedResult; // Company validation result $result->getStreet(): ?QualifiedResult; // Street validation result $result->getZip(): ?QualifiedResult; // ZIP validation result $result->getLocation(): ?QualifiedResult; // Location validation result $result->getRaw(): ?string; // Raw API response (if requested) $result->toArray(): array; // Convert to array
Status Codes
The API returns various status codes via the Status
enum. All status codes are available as enum cases:
use Rechtlogisch\Evatr\Enum\Status; // Check the status if ($result->getStatus() === Status::EVATR_0000) { // VAT-ID is valid } // Get human-readable description $description = $result->getStatus()->description();
Validation Results (Qualified Confirmation)
For qualified confirmations, the response includes validation results for each field via the QualifiedResult
enum:
- A - Data matches registered information
- B - Data does not match registered information
- C - Data was not requested
- D - Data not provided by the EU member state
use Rechtlogisch\Evatr\Enum\QualifiedResult; if ($result->getCompany() === QualifiedResult::A) { // Company name matches }
Helper Functions
The package provides convenient helper functions:
checkVatId()
function checkVatId( string $vatIdOwn, string $vatIdForeign, bool $includeRaw = false ): ResultDto;
confirmVatId()
function confirmVatId( string $vatIdOwn, string $vatIdForeign, ?string $company, ?string $street, ?string $zip, ?string $location, bool $includeRaw = false ): ResultDto;
Field Mapping
The API uses German terms, which have been mapped to parameters:
Request
BZSt API | evatr |
---|---|
anfragendeUstid | vatIdOwn |
angefragteUstid | vatIdForeign |
firmenname | company |
ort | location |
strasse | street |
plz | zip |
Response
BZSt API | evatr |
---|---|
id | id |
anfrageZeitpunkt | timestamp |
gueltigAb | dateFrom |
gueltigBis | dateTill |
ergFirmenname | company |
ergStrasse | street |
ergPlz | zip |
ergOrt | location |
Language of status messages (EVATR_LANG)
By default, status messages (human-readable descriptions of evatr-* codes) are returned in German. To switch to English messages, set the following environment variable:
# .env
EVATR_LANG=en
Warning
This English translation of the status messages is unofficial. Use at your own risk.
Supported values:
- de (default): German messages
- en: English messages
This affects:
- Status::description()
- ResultDto->toArray()['message']
Additional endpoints and helpers
The client exposes supplementary endpoints of the eVatR API.
Status messages
$messages = Evatr::getStatusMessages(); // array of DTO\StatusMessage
Each StatusMessage item has the shape:
use Rechtlogisch\Evatr\DTO\StatusMessage; $statusMessage = new StatusMessage( status: 'evatr-0000', category: 'Result', // category is always English and language-invariant: Result | Error | Hint http: 200, field: null, message: 'Die angefragte Ust-IdNr. ist zum Anfragezeitpunkt gültig.' );
EU member states availability
$states = Evatr::getAvailability(); // array<string,bool> map of code => available // Example: [ 'DE' => true, 'AT' => false, ... ] // Only not available: $notAvailable = Evatr::getAvailability(onlyNotAvailable: true); // [ 'AT' => false, ... ]
Error Handling
All public API methods throw exceptions on failure, and return only DTOs on success.
- ErrorResponse: thrown for transport, server, or JSON/response parsing errors.
- InputError: thrown when the error is caused by invalid input, and can be potentially fixed by the user.
use Rechtlogisch\Evatr\Exception\ErrorResponse; use Rechtlogisch\Evatr\Exception\InputError; use Rechtlogisch\Evatr\Enum\Status; try { $result = checkVatId('DE123456789', 'ATU12345678'); // handle result } catch (InputError|ErrorResponse $e) { // Log/handle error: $e->getMessage() // Get original exception: $e->getException() }
ErrorResponse exception
Thrown when a request fails due to transport or response parsing issues.
Fields:
- httpCode: int HTTP status or 0 for client-side failures
- error: string short description
- exception: Throwable the underlying/previous exception; if no underlying Throwable existed, a RuntimeException is used
- raw: ?string raw response body if available
- meta: array<string,mixed> additional context (e.g., endpoint, errorType)
Example:
use Rechtlogisch\Evatr\Exception\ErrorResponse; try { $result = checkVatId('DE123456789', 'ATU12345678'); } catch (ErrorResponse $e) { $code = $e->getHttpCode(); $msg = $e->getError(); $previous = $e->getException(); // underlying Throwable (e.g., JsonException, GuzzleException, or RuntimeException) $raw = $e->getRaw(); // may be null $meta = $e->getMeta(); // ['endpoint' => ..., 'errorType' => ...] // Handle/log accordingly }
Testing
Running Tests
composer test
Test Data
The library includes test VAT-IDs that can be used for development and testing:
// Simple validation test $testRequest = [ 'vatIdOwn' => 'DE123456789', 'vatIdForeign' => 'ATU12345678', ]; // Qualified confirmation test $qualifiedTestRequest = [ 'vatIdOwn' => 'DE123456789', 'vatIdForeign' => 'ATU12345678', 'company' => 'Musterhaus GmbH & Co KG', 'street' => 'Musterstrasse 22', 'zip' => '12345', 'location' => 'musterort', ];
Environment Variables
For testing with real VAT-IDs, you can set environment variables:
# .env
VATID_OWN=DE123456789
VATID_FOREIGN=ATU12345678
Rate Limits and Best Practices
- The API has rate limits. Implement appropriate delays between requests
- Cache results when possible to reduce API calls
- Handle all possible status codes in your application
- Always validate VAT-ID formats before making API calls
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
If you discover any security-related issues, please email open-source@rechtlogisch.de instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.