vat.toolsDocumentation
Open workspace

Overview

What the public verification API is, and the shape of every answer it returns.

VAT.tools checks VAT identifiers and company records against public authorities and certified registries. The API does not return a boolean: it returns a versioned envelope where every outcome keeps the source that produced it, the scope it covers, and the time it was observed.

That single rule shapes everything here — the request rules, the error codes, and the way a result must be read. Learn it once on this page and the reference becomes self-explanatory.

Your first request

Every write operation is a POST to a fixed path with a JSON body, a bearer developer key, and an optional idempotency key. This one validates a VAT identifier in the sandbox.

curl --request POST \
  'https://vat.tools/api/v2/validations' \
  --header 'Authorization: Bearer vat_sandbox_REPLACE_WITH_YOUR_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: 8f0c1a52-4b6e-4f17-9d2a-3c5e7b9a1d40' \
  --data '{"countryCode":"DE","vatNumber":"123456789","mode":"sandbox"}'

The response is a complete envelope. Read the parts, not just the outcome:

200 · application/json
{
  "schemaVersion": "2026-09-01",
  "operation": {
    "id": "0f6c2a90-1b3e-4c5d-9a7f-2e4b6c8d0a12",
    "kind": "vat_validation",
    "status": "completed",
    "environment": "sandbox",
    "createdAt": "2026-09-14T09:12:04.118Z",
    "completedAt": "2026-09-14T09:12:04.481Z",
    "replayed": false
  },
  "subject": { "taxJurisdiction": "DE", "vatNumber": "123456789" },
  "result": {
    "kind": "vat_validation",
    "outcome": "valid",
    "vatRegistration": { "countryCode": "DE", "vatNumber": "123456789" },
    "legalName": null,
    "checkedAt": "2026-09-14T09:12:04.470Z",
    "cached": false,
    "evidenceRefs": ["ev-1"]
  },
  "claims": [],
  "relationships": [
    {
      "type": "vat_registration_of",
      "outcome": "confirmed",
      "vatRegistration": { "countryCode": "DE", "vatNumber": "123456789" },
      "evidenceRefs": ["ev-1"]
    }
  ],
  "conflicts": [],
  "issues": [],
  "evidence": [
    {
      "id": "ev-1",
      "source": { "name": "VAT Tools synthetic sandbox fixture", "url": null },
      "capability": "vat_validation",
      "environment": "sandbox",
      "observedAt": "2026-09-14T09:12:04.470Z",
      "retrievedAt": "2026-09-14T09:12:04.481Z",
      "servedAt": "2026-09-14T09:12:04.481Z",
      "deliveryForm": "sandbox_fixture"
    }
  ],
  "staleEvidence": [],
  "freshness": {
    "effectiveMaxAgeSeconds": 86400,
    "satisfied": true,
    "sourceCadenceSeconds": 21600
  },
  "usage": { "meter": "vat_validation", "unitsCharged": 1, "settlementReason": "completed" }
}

Anatomy of an answer

Every successful response carries the same six parts. They are what an integration must preserve when it stores or displays a result.

Every successful response is one versioned envelope. These six parts are always present, and always attributed.
Operation

Who ran what, in which environment, and whether this response is a replay of an earlier one.

"operation": {
  "id": "0f6c2a90-1b3e-4c5d-9a7f-2e4b6c8d0a12",
  "kind": "vat_validation",
  "status": "completed",
  "environment": "sandbox",
  "createdAt": "2026-09-14T09:12:04.118Z",
  "completedAt": "2026-09-14T09:12:04.481Z",
  "replayed": false
}
Result

The operation's own answer. Only an authority outcome says valid or invalid; unsupported and source_unavailable never do.

"result": {
  "kind": "vat_validation",
  "outcome": "valid",
  "vatRegistration": { "countryCode": "DE", "vatNumber": "123456789" },
  "legalName": null,
  "checkedAt": "2026-09-14T09:12:04.470Z",
  "cached": false,
  "evidenceRefs": ["ev-1"]
}
Claims & relationships

Scoped assertions and the links between entities and registrations. Each one keeps the evidence that supports it.

"claims": [],
"relationships": [
  {
    "type": "vat_registration_of",
    "outcome": "confirmed",
    "vatRegistration": { "countryCode": "DE", "vatNumber": "123456789" },
    "evidenceRefs": ["ev-1"]
  }
],
"conflicts": [], "issues": []
Evidence

The source leaves its fingerprint: who answered, when it was observed, and how it was delivered.

"evidence": [
  {
    "id": "ev-1",
    "source": { "name": "Bundeszentralamt für Steuern", "url": null },
    "capability": "vat_validation",
    "environment": "sandbox",
    "authorityScope": "EU",
    "observedAt": "2026-09-14T09:12:04.470Z",
    "retrievedAt": "2026-09-14T09:12:04.481Z",
    "servedAt": "2026-09-14T09:12:04.481Z",
    "deliveryForm": "sandbox_fixture",
    "completion": { "state": "complete", "boundary": "authority" }
  }
]
Freshness

How long this answer stays current, plus the source's own cadence — which a faster delivery cannot change.

"freshness": {
  "effectiveMaxAgeSeconds": 86400,
  "satisfied": true,
  "sourceCadenceSeconds": 21600,
  "limitation": "Authority cadence is unchanged by this delivery."
}
Usage

What the call consumed and how it settled. A replay settles at zero.

"usage": {
  "meter": "vat_validation",
  "unitsCharged": 1,
  "settlementReason": "completed"
}

The operations

Five write operations cover the product's jobs, plus candidate selection and a read-only history. Each has one scope and one meter.

Open the full API reference →

The honesty boundary

The product language is precise, and the API enforces it. An integration that ignores this is incorrect, even if it compiles.

  • Treat valid and invalid as authority outcomes. Only an explicit authority false makes a VAT identifier invalid.
  • source_unavailable and unsupported are distinct, non-blocking states. Never render them as invalid.
  • Keep evidence source and observation time attached to the outcome you display; a later retrieval is not a new observation time.
  • Compare claims field by field. Never collapse a company's name, status, and VAT association into a single verified flag.