# EU VAT number validation API (VIES): dated answers, 100 free calls

Source: https://veriport.io/vat-validation-api (published 2026-10-28)

A VAT validation API checks that a VAT identification number is registered for intra-EU trade and gives you a record of the check. Veriport does it in one GET: the national structure and check digit first, then the European Commission’s VIES (REST, SOAP as fallback), the registered name and address where published, and, when a member state is down, a dated cached answer that says it is cached. Every answer states its provenance; on paid tiers the VIES consultation number is the audit evidence.

- `source.kind` is `live`, `cache` or `structure_only` and `source.stale` is true whenever VIES did not answer during the request: your code decides what a dated answer is worth, the API never pretends.
- An impossible number (wrong structure or check digit) answers 200 with `valid: false` and `upstream.status: "not_called"`: no VIES call, no quota, no 4xx to handle.
- Consultation numbers (`requester=` your own VAT number) and stored validation records (`record=true`) on prepaid packs and the plan; bulk up to 100 numbers a request on the plan.
- Prices: $2.90 to $4.50 per 1,000 validations, tax included, against $2.40–30 at the named alternatives; 100 free a month.

## The answer, field by field

A German number validated live (Germany publishes no trader name through VIES, so `name` and `address` are null although the number is valid):

| Field | What it says |
|---|---|
| `valid` | VIES’s verdict at `requestDate`: registered for intra-EU trade, or not. An invalid number is a 200, not an error. |
| `countryCode`, `vatNumber`, `full` | The normalised number: upper case, prefix split off, spaces and dots removed; `GR` becomes `EL`. |
| `structure` | The pre-check: `valid`, `checksum` (`passed`, `failed` or `not_checked` where no public algorithm exists), `expected` (the national structure in words). |
| `name`, `address` | As the member state publishes them; null where it publishes nothing (Germany, most Spanish companies) or when the answer is structure only. |
| `consultationNumber` | The VIES reference of this check when you sent `requester`; the proof an audit accepts. Null on the free tier. |
| `source` | `kind` (`live`, `cache`, `structure_only`), `checkedAt`, `cacheAgeSeconds`, `stale`: where the answer came from and how old it is. |
| `upstream` | `service`, `transport` (`rest` or `soap`), `status` (`ok`, `member_state_unavailable`, `not_called`…), `memberStateAvailable`. |

```json
{
  "valid": true,
  "countryCode": "DE",
  "vatNumber": "136695976",
  "full": "DE136695976",
  "structure": {
    "valid": true,
    "checksum": "passed",
    "expected": "9 digits"
  },
  "name": null,
  "address": null,
  "requestDate": "2026-10-24",
  "consultationNumber": null,
  "source": {
    "kind": "live",
    "checkedAt": "2026-10-24T09:14:03Z",
    "cacheAgeSeconds": 0,
    "stale": false
  },
  "upstream": {
    "service": "vies",
    "transport": "rest",
    "status": "ok",
    "memberStateAvailable": true
  },
  "tableVersion": null
}
```

## When VIES or a member state is down: the degraded path

VIES relays each query to a national register; registers go down for hours, sometimes days (Italy, Germany and Spain most often). Then the API serves the answer it obtained for the same number in the last 30 days, marked `source.kind: "cache"`, `stale: true`, with the date it was obtained; without a cached answer it returns a 503 `vies_unavailable` with `retryAfter`, which is never counted. A cached answer looks like this:

```json
{
  "valid": true,
  "countryCode": "ES",
  "vatNumber": "B58378431",
  "full": "ESB58378431",
  "structure": {
    "valid": true,
    "checksum": "not_checked",
    "expected": "9 characters; the first, the last or both are letters"
  },
  "name": "EJEMPLO SL",
  "address": "CALLE EJEMPLO 1, 28001 MADRID",
  "requestDate": "2026-10-17",
  "consultationNumber": null,
  "source": {
    "kind": "cache",
    "checkedAt": "2026-10-17T16:40:11Z",
    "cacheAgeSeconds": 578632,
    "stale": true
  },
  "upstream": {
    "service": "vies",
    "transport": "rest",
    "status": "member_state_unavailable",
    "memberStateAvailable": false
  },
  "tableVersion": null
}
```

> A cached answer is evidence that the number was valid on `source.checkedAt`, not today. For a one-off sale most sellers accept it; for onboarding a new customer, retry when the member state is back (the status page lists them).

## The same call in six languages

One GET, the key optional for the first 20 calls a day. Errors are RFC 9457 problem documents with a stable `code`.

```bash
curl "https://veriport.io/api/v1/vat/validate?country=DE&number=136695976"
# with a key, once you have one:
# curl -H "Authorization: Bearer vp_live_YOUR_KEY" "https://veriport.io/api/v1/vat/validate?country=DE&number=136695976"
```

```javascript
const KEY = process.env.VERIPORT_KEY; // optional for the first calls of the day
const res = await fetch("https://veriport.io/api/v1/vat/validate?country=DE&number=136695976", { headers: KEY ? { Authorization: "Bearer " + KEY } : {} });
const body = await res.json();
if (!res.ok) {
  // RFC 9457 problem document: body.code is stable, body.detail is for humans
  if (body.code === "vies_unavailable") console.warn("upstream down, retry in", body.retryAfter, "s");
  throw new Error(body.code + ": " + body.detail);
}
console.log(body.valid, body.source.kind, body.source.stale);
```

```python
import os, requests

key = os.environ.get("VERIPORT_KEY")  # optional for the first calls of the day
r = requests.get("https://veriport.io/api/v1/vat/validate?country=DE&number=136695976", headers={"Authorization": f"Bearer {key}"} if key else {}, timeout=15)
body = r.json()
if not r.ok:
    # RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
    if body.get("code") == "vies_unavailable":
        print("upstream down, retry in", body.get("retryAfter"), "s")
    raise RuntimeError(f"{body.get('code')}: {body.get('detail')}")
print(body["valid"], body["source"]["kind"], body["source"]["stale"])
```

```php
<?php
$key = getenv("VERIPORT_KEY") ?: null; // optional for the first calls of the day
$ch = curl_init("https://veriport.io/api/v1/vat/validate?country=DE&number=136695976");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => $key ? ["Authorization: Bearer $key"] : []]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
    // RFC 9457 problem document: $body["code"] is stable, $body["detail"] is for humans
    throw new RuntimeException(($body["code"] ?? "error") . ": " . ($body["detail"] ?? ""));
}
var_dump($body["valid"], $body["source"]["kind"], $body["source"]["stale"]);
```

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://veriport.io/api/v1/vat/validate?country=DE&number=136695976", nil)
	if key := os.Getenv("VERIPORT_KEY"); key != "" { // optional for the first calls of the day
		req.Header.Set("Authorization", "Bearer "+key)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var body map[string]any
	json.NewDecoder(res.Body).Decode(&body)
	if res.StatusCode >= 400 {
		// RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
		panic(fmt.Sprintf("%v: %v", body["code"], body["detail"]))
	}
	fmt.Println(body["valid"], body["source"].(map[string]any)["kind"], body["source"].(map[string]any)["stale"])
}
```

```csharp
using System.Net.Http.Headers;
using System.Text.Json;

var http = new HttpClient();
var key = Environment.GetEnvironmentVariable("VERIPORT_KEY"); // optional for the first calls of the day
if (!string.IsNullOrEmpty(key)) http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
var res = await http.GetAsync("https://veriport.io/api/v1/vat/validate?country=DE&number=136695976");
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
    // RFC 9457 problem document: "code" is stable, "detail" is for humans
    throw new Exception($"{body.RootElement.GetProperty("code")}: {body.RootElement.GetProperty("detail")}");
}
Console.WriteLine(body.RootElement.GetProperty("valid") + " " + body.RootElement.GetProperty("source").GetProperty("kind") + " " + body.RootElement.GetProperty("source").GetProperty("stale"));
```

## Price per 1,000 calls, against the named alternatives

Competitors’ figures are read from their public pricing pages on 2026-10-28 at full use of the included volume; a range means several tiers. Ours include the tax of your country, charged by Stripe as merchant of record.

| Offer | Price | Included | Per 1,000 | Billing |
|---|---|---|---|---|
| [Vatstack](https://vatstack.com/pricing) Small Business → Enterprise | $15 → $150 / month | 500 → 15,000 validations | $30.00 → $10.00 | Subscription |
| [vatcheckapi](https://vatcheckapi.com/) tiers | monthly tiers | per tier | $2.40 → $5.00 | Subscription |
| [VATSense](https://vatsense.com/), [vatlayer](https://vatlayer.com/), [Abstract](https://www.abstractapi.com/api/vat-validation-api) | monthly tiers | per tier | $1.65 → $14.00 | Subscription |
| VIES itself (REST) | free | no commitment | $0 | No cache, no consultation number without SOAP, rate limits per IP |
| **Veriport** 2,000 pack | $9 once | 2,000 calls, 12 months | $4.50 | Prepaid, no renewal |
| **Veriport** 10,000 pack | $29 once | 10,000 calls, 12 months | $2.90 | Prepaid, no renewal |
| **Veriport** monthly plan | $15 / month | 5,000 calls + bulk | $3.00 | Subscription, cancel any time |

> Free before any of this: 100 calls a month with a key, 20 a day without one. A 4xx or 5xx is never counted.

## What it is not

Not an official service and not affiliated with the Commission: the verdict is VIES’s, the provenance and the cache are ours. Not a KYB file: a valid number says the number is registered, not that your customer is that company. Not a UK, Swiss or Norwegian check: VIES covers the 27 member states and Northern Ireland; HMRC is on the roadmap.

## Questions

### Why not call VIES directly?

You can, and the quick start shows the Commission’s endpoint. What you do not get from VIES: a structural pre-check that saves the call, a cached answer when a member state is down, consultation numbers over REST, stable error codes, and a response that says where it came from. Those are the hours this API saves in an invoice run.

### Does an invalid answer cost a call?

A 200 with `valid: false` from VIES costs one call. A structure-only refusal (`upstream.status: "not_called"`) and any 4xx or 5xx cost nothing.

### Is the consultation number enough for an audit?

It is the reference the Commission’s service issues to the requester for that check and the one tax administrations ask for; keep it with the date, the number checked and the answer. The API returns it on the paid tiers when you pass your own VAT number as `requester`.

### Where is the data kept?

Functions and database in Paris, France (EU). Validated numbers and their answers are cached up to 30 days for the degraded path; keys are stored hashed; no analytics, no cookies.

## Read next

- [Reference: GET /vat/validate](https://veriport.io/docs/vat-validation)
- [Validate EU VAT numbers in bulk from a CSV](https://veriport.io/guides/validate-eu-vat-numbers-in-bulk-from-csv)
- [VIES API: REST vs SOAP, errors and limits](https://veriport.io/guides/vies-api-check-eu-vat-number)
- [Free VAT number check](https://veriport.io/tools/vat-number-check)
- [VAT number formats by country](https://veriport.io/vat-number-format)
- [VIES is down: validate anyway](https://veriport.io/guides/vies-down-validate-eu-vat-number-anyway)
- [Is VIES down? Availability by member state](https://veriport.io/vies-status)
- [EU VAT validation APIs compared (2026)](https://veriport.io/alternatives/eu-vat-validation-apis-compared)
- [Vatstack alternative](https://veriport.io/alternatives/vatstack)
- [Pricing](https://veriport.io/pricing)

## Sources

- VIES REST API — check-vat-number, check-status (technical information) — https://ec.europa.eu/taxation_customs/vies/#/technical-information (2026-10-24)
- VIES SOAP service — checkVatService WSDL — https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl (2026-10-24)
- VIES — help page: what the consultation number proves, member-state availability — https://ec.europa.eu/taxation_customs/vies/#/help (2026-10-24)
- Vatstack pricing: Launch $0 (100 validations, then $0.09), Small Business $15/mo (500, $0.03 over), Enterprise $150/mo (15,000, $0.01 over, 99.95 % SLA); EU data centre on every plan; prices exclude VAT — https://vatstack.com/pricing (2026-10-28)
