evatr
v9.0.0
Published
Validate foreign VAT numbers via German “Bundeszentralamt für Steuern”
Maintainers
Readme
Bestätigung von ausländischen Umsatzsteuer-Identifikationsnummern
Validates foreign (in regards to Germany) VAT numbers for their validity. Allows to perform a “simple” check, for just checking the number, and a “qualified” check, which validates the number in regard to a given company name and city (and optionally zip code and street).
The service is provided by the German “Bundeszentralamt für Steuern”. The German documentation of the API can be found here. Since v8.0.0 it uses the REST API instead of the obsolete XML-RPC interface.
To use this tool, you need to be in possession of a valid German VAT number.
Installation
$ npm install evatrUsage
import * as evatr from 'evatr';
const simpleResult = await evatr.checkSimple({
ownVatNumber: 'DE115235681',
validateVatNumber: 'CZ00177041',
});
const qualifiedResult = await evatr.checkQualified({
ownVatNumber: 'DE115235681',
validateVatNumber: 'CZ00177041',
companyName: 'ŠKODA AUTO a.s.',
city: 'Mlada Boleslav',
zip: '293 01',
street: 'tř. Václava Klementa 869',
});A failed check is not an error: an invalid VAT number, or an error response from the service, is reported through errorCode and errorDescription on the result. The returned promise rejects only when no result can be produced at all — the request does not complete (a network or DNS error), or the response cannot be read as a result. For checkQualified that last case includes an unknown per-field result letter, which the service can return on an otherwise successful answer, so handle rejection rather than assuming a reply means success.
TS typings are available. Besides the two check functions, the package exports:
resultTypesand theResultTypetype — the per-field results of a qualified check (Amatch,Bno match,Cnot queried,Dnot returned), as returned inresultName,resultCity,resultZipandresultStreeterrorCodesand theErrorCodeEntrytype — the BZSt status message table from whicherrorDescriptionis populated
Development
Node.js and pnpm are pinned in package.json (devEngines.runtime and packageManager); pnpm downloads the pinned Node.js version itself, so no separate version manager is needed.
Install dependencies with pnpm install.
To execute the tests, run pnpm test. To lint, run pnpm run lint, which runs ESLint, Prettier, the TypeScript compiler and Knip in turn; each is also available on its own as lint:eslint, lint:format, lint:types and lint:knip.
Dependencies are updated with pnpm outdated and pnpm update -i --latest. @types/node deliberately tracks the engines.node floor rather than the newest release, so it is always listed as behind — do not upgrade it past the supported major.
The tests talk to the live BZSt API, which is deliberate — this is a client for a remote service, so upstream downtime failing the build is information rather than flakiness.
For the best development experience, make sure that your editor supports ESLint, Prettier and EditorConfig.
tsconfig.json sets types explicitly. TypeScript 6 no longer pulls in every installed @types package on its own, so without it an editor bundling TypeScript 6 reports Cannot find name 'fetch' and Cannot find name 'path' in files that the pinned TypeScript 5.9 compiles cleanly. .vscode/settings.json also points the editor at the pinned compiler, so its diagnostics match pnpm run lint:types; in VS Code, accept Use Workspace Version if prompted.
Linting of code and commit message happens on commit via Husky. Commit messages follow Conventional Commits; .commitlintrc.json lists the accepted types.
Error Codes
There’s a script which scrapes and includes human-readable error codes from here. This way, obscure codes such as evatr-2003 are mapped to an understandable German message (for this example: “Das angegebene Länderkennzeichen der angefragten USt-IdNr. ist nicht gültig.”)
To update the list, run pnpm run scrape-error-codes, which regenerates lib/error-codes.ts.
A test compares the shipped table against that endpoint, so the build goes red when the BZSt changes a message rather than the stale text quietly continuing to ship.
Releasing to NPM
First promote the changelog by hand: rename ## [Unreleased] to ## [X.Y.Z] – <date>, repoint the [unreleased] link reference at the new tag, and add a comparison link for the release. Commit that on its own — the wording, grouping and date deserve review, and nothing rewrites them for you.
Then commit any remaining changes and run the following:
$ pnpm login
$ pnpm run release <update_type>
$ git push --follow-tags
$ pnpm publish… where <update_type> is one of patch, minor, or major — passed positionally, not as --patch. This updates the package.json and creates a tagged Git commit.
--follow-tags matters: a plain git push leaves the version tag behind, and the changelog’s comparison links stay broken until it lands. pnpm publish refuses to publish a branch that is not clean and up to date, so it will catch an unpushed commit — but not an unpushed tag.
The release refuses to run unless the changelog is ready. dev/verify-changelog.ts checks it and never modifies it, from two lifecycle scripts: preversion, before the version is bumped, so the usual “forgot to promote it” case fails while the working tree is still clean; and version, after the bump, which is the only point at which the new version number is known and can be compared against the heading. The error message spells out what to write, with the repository URL and previous tag filled in.
A failure at the version stage — a changelog promoted to a different version than the one being released — aborts before any commit or tag, but leaves package.json bumped, since the bump happens first. Undo it with git checkout package.json before retrying.
Use pnpm, not npm, for these. Because the project pins its Node.js version through devEngines.runtime, npm refuses to run anything here (EBADDEVENGINES) unless the ambient Node.js version happens to match that exact version.
Contributing
Pull requests are very welcome. Feel free to discuss bugs or new features by opening a new issue.
Copyright Philipp Katz, LineUpr GmbH, 2018 – 2026
