API reference · v1

Vatlas API documentation

One REST API, three entry points, one JSON envelope. Everything is a GET, everything is UTF-8, and every date is UTC in ISO 8601.

Base URL

https://api.vatlas.dev

Paths are versioned (/v1/…). A version never changes shape in a breaking way: a new field may appear, none is ever removed.

Authentication

Every call carries a key as a bearer token in the Authorization header. You can create as many keys as you like — they all draw on the same account quota, so a key is there to tell you which of your integrations made a call.

curl "https://api.vatlas.dev/v1/vat/FR08000325175" \
  -H "Authorization: Bearer vtl_live_xxxxxxxxxxxxxxxxxxxx"

Keep the key server-side. It must never ship in a front-end bundle: anyone could read it and burn your quota. Proxy the call through your backend.

A missing, unknown or revoked key answers 401 with the standard error envelope, and is not counted. Create your key — the Free plan is free and needs no card.

Response envelope

Success always carries data and meta. An error always carries error, and meta whenever sources were consulted. The shape does not change with the origin of the data: your code has nothing to branch on.

  • meta.origindatabase, source or vies.
  • meta.sourceSIRENE, MF_WL, PRH, RIK, UR_VID or VIES.
  • meta.sourceUpdatedAt — UTC timestamp of the data served.
  • meta.checked — every source consulted, in order.

Resolve a VAT number

GET /v1/vat/{vatNumber}

The heart of the API. Input is normalised before validation: case, spaces, dots and dashes are ignored, and Greece's ISO prefix GR is accepted alongside its VAT prefix EL. data.vatNumber always comes back in canonical form.

Parameters
Parameter Type Description
vatNumber path, required Intra-community VAT number, country prefix included. FR08000325175 and fr 08 000.325-175 are equivalent.

Request

curl "https://api.vatlas.dev/v1/vat/FR08000325175" \
  -H "Authorization: Bearer $VATLAS_KEY"

Response — 200 OK

{
  "data": {
    "vatNumber": "FR08000325175",
    "countryCode": "FR",
    "nationalNumber": "08000325175",
    "nationalId": "000325175",
    "name": "THIERRY JANOYER",
    "legalForm": "1000",
    "status": "active",
    "address": {
      "line1": "51 RUE MARX DORMOY",
      "line2": null,
      "postalCode": "13004",
      "city": "MARSEILLE",
      "country": "FR"
    }
  },
  "meta": {
    "origin": "database",
    "source": "SIRENE",
    "sourceUpdatedAt": "2025-12-06T09:43:55.000Z",
    "checked": ["database"]
  }
}

An empty name is not a bug. Germany, Spain and the Netherlands disclose neither legal name nor address through VIES. For those countries, until a local register is wired in, only status is meaningful.

Response fields

Fields of data
Field Type Description
vatNumberstringCanonical number, country prefix included.
countryCodestringTwo-letter VAT prefix (EL for Greece, XI for Northern Ireland).
nationalNumberstringThe national part of the number, without the prefix.
nationalIdstring | nullIdentifier in the national register (SIREN, Business ID, registrikood…). May differ from the VAT number.
namestringLegal name as published by the source. Empty string when the source does not disclose it.
legalFormstring | nullLegal form code from the originating register, not harmonised across countries.
statusenumactive, inactive or unknown.
address.line1string | nullStreet, or the whole address when the source does not split it.
address.line2string | nullAddress complement.
address.postalCodestring | nullPostal code.
address.citystring | nullTown. null for Latvia, whose register publishes a territory code.
address.countrystringISO 3166-1 alpha-2 country code.

List every country and how it is served

GET /v1/countries

Returns all 28 prefixes — the 27 member states plus Northern Ireland — with the source behind each and its strategy: bulk for an imported register, on-demand for the register's own API, both, or vies for a member state with no usable local source, which still resolves through the VIES fallback.

{
  "data": [
    { "countryCode": "FR", "name": "France",   "source": "SIRENE",  "strategy": "bulk" },
    { "countryCode": "PL", "name": "Poland",   "source": "MF_WL",   "strategy": "on-demand" },
    { "countryCode": "CZ", "name": "Czechia",  "source": "ARES",    "strategy": "on-demand" },
    { "countryCode": "FI", "name": "Finland",  "source": "PRH",     "strategy": "both" },
    { "countryCode": "DE", "name": "Germany",  "source": "VIES",    "strategy": "vies" }
  ]
}

Liveness probe

GET /health

Answers only once a database round trip has succeeded, so it works as-is for a readiness probe. This call costs no quota.

{ "status": "ok" }

Error codes

Every error shares the same shape:

{
  "error": {
    "code": "INVALID_VAT_FORMAT",
    "message": "Unknown VAT country prefix \"XX\"",
    "reason": "UNKNOWN_COUNTRY"
  }
}
Error codes
Code HTTP Meaning
INVALID_VAT_FORMAT 400 Invalid syntax or checksum; no source was consulted. reason says which: EMPTY, UNKNOWN_COUNTRY, BAD_FORMAT, BAD_CHECKSUM.
MISSING_SECRET_KEY 401 No Authorization: Bearer credential was sent, or the header used another scheme. This call is not counted against your quota.
INVALID_SECRET_KEY 401 The key is unknown or has been revoked. Revoking a key takes effect within a minute, so a key revoked seconds ago may still answer.
QUOTA_EXCEEDED 429 The monthly quota for the account is spent. It renews at the start of the next month; X-RateLimit-Reset carries the exact instant.
VAT_NOT_REGISTERED 404 VIES gave a definitive answer: the number is not registered for intra-community trade. This is not the same as the company not existing — it can be perfectly active in its national register without an intra-community VAT registration.
COMPANY_NOT_FOUND 404 Nothing left to ask: the number is valid, but was found nowhere.
SOURCE_UNAVAILABLE 503 Not in the database, and VIES did not answer. This is not a negative result and is never cached: retry later.
NOT_FOUND 404 Unknown route.
INTERNAL_ERROR 500 Unexpected failure on our side.

Quotas & limits

The quota is monthly and belongs to the account, not to an individual key. Every metered response carries the current state of the counter, so there is nothing to estimate:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1788220800
  • A 400 format error is never counted: the number is rejected before any source is consulted.
  • A 503 is not counted either — a member state failed to answer, and that is not your call to pay for.
  • /v1/countries and /health need no key and cost no quota.
  • Quota exhausted: 429, with the same error envelope. X-RateLimit-Reset says when it renews.

Plan details on the pricing page.

Code samples

curl -s "https://api.vatlas.dev/v1/vat/FR08000325175" \
  -H "Authorization: Bearer $VATLAS_KEY" \
  | jq '.data.name'

Ready to wire this up in ten minutes?

100 requests a month for free, no card required. 10,000 a month for 14 € excl. VAT when you outgrow it.