Bulk VAT Validation
POST /v1/vat/bulk validates up to 100 EU VAT numbers in a single API call. Each ID runs through the same VIES + national-fallback pipeline as GET /v1/vat/:vatId, so results are identical — you just get them all back together, as one result per VAT ID plus a summary of how many were valid, invalid, or errored. One bad or unreachable VAT ID never fails the rest of the batch: the response is always 200 OK.
When to Use It
- Cleaning an imported customer or supplier list before it hits your billing system
- Periodic re-validation of VAT numbers you already store, in scheduled batches
- Pulling a fresh, timestamped result set together ahead of a VAT audit
For a single VAT ID looked up on demand — at checkout, on account creation — use GET /v1/vat/:vatId instead; it also returns company name, address and other enrichment fields that the bulk endpoint omits.
Endpoint
POST /v1/vat/bulkRequires a live API key, the same as every other billable endpoint — see Authentication.
Request
Body
| Field | Type | Required | Description |
|---|---|---|---|
| vatIds | string[] | Yes (body) | 1–100 VAT numbers, each with its country prefix (e.g. IE6388047V). Normalized the same way as the single endpoint (uppercased, whitespace/dashes stripped). A request with 0 or more than 100 entries is rejected as a whole (400) before any item is processed. |
No query parameters. The requester VAT used for consultation numbers comes entirely from your account-level dashboard Settings and applies to the whole batch— every item that reaches VIES directly returns a consultation number once it's set.
curl https://api.vatnode.dev/v1/vat/bulk \
-H "Authorization: Bearer vat_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"vatIds": ["IE6388047V", "DE143454214", "DE12345"]
}'Response
Always 200 OK — a request-level failure (auth, malformed body) is the only thing that returns a non-200 status. Per-item outcomes live in results.
{
"results": [
{
"vatId": "IE6388047V",
"valid": true,
"source": "VIES",
"consultationNumber": null
},
{
"vatId": "DE143454214",
"valid": true,
"source": "CACHE",
"consultationNumber": null
},
{
"vatId": "DE12345",
"valid": null,
"source": null,
"consultationNumber": null,
"error": {
"code": "INVALID_FORMAT",
"message": "Invalid VAT ID format. Expected format: country code (2 letters) followed by VAT number"
}
}
],
"summary": {
"requested": 3,
"valid": 2,
"invalid": 0,
"errors": 1
}
}Response Fields
| Field | Type | Description |
|---|---|---|
| results | array | One entry per unique VAT ID submitted, in first-seen order. |
| results[].vatId | string | The normalized VAT number |
| results[].valid | boolean | null | true/false on a completed check; null when error is set — no determination was made. |
| results[].source | string | null | VIES, CACHE, or a national fallback code — same meaning as on the single endpoint. Null when the item errored. |
| results[].consultationNumber | string | null | Present only when a requester VAT is set in dashboard Settings and that item was validated directly against VIES (not served from cache). Never cached itself. |
| results[].error | object (optional) | Present only when this item failed. Carries code and message — see Error Codes. |
| summary.requested | number | Number of unique VAT IDs processed — results.length. Duplicates in the submitted array are collapsed before this count. |
| summary.valid | number | Items with valid: true |
| summary.invalid | number | Items with valid: false |
| summary.errors | number | Items with an error — none of these were billed (see below). |
The bulk endpoint returns a leaner per-item shape than the single endpoint — no companyName, companyAddress or other enrichment fields. If you need company details for a specific VAT ID, follow up with GET /v1/vat/:vatId for that one number.
Billing & Quota
Same price per check, no batch fee
Every VAT ID that gets a real valid result — true or false — counts as one validation against your monthly quota, exactly like a call to GET /v1/vat/:vatId. If the batch pushes you past your plan's monthly quota, the extra items are billed at your plan's overage rate on Starter (€0.025/request) or Pro (€0.015/request) — the free plan is hard-capped, so those items return RATE_LIMITED instead. There is no separate per-batch charge.
Anything that comes back with an error field costs nothing — it either never touched your quota or the reservation was refunded. In practice that covers:
- Malformed VAT IDs (
INVALID_FORMAT) — rejected before any quota is reserved. - Upstream failures — VIES or a national fallback being unreachable (
VIES_UNAVAILABLE,UPSTREAM_TIMEOUT), an invalid requester (INVALID_REQUESTER), or any other unexpected error — the reservation for that item is refunded. - Quota-exceeded items (
RATE_LIMITED) — by definition never reserved a slot in the first place.
Duplicate VAT IDs within the same request are de-duplicated before processing, so listing the same VAT ID twice produces one result and is billed once — not twice.
The RateLimit-Limit/RateLimit-Remaining headers described in Rate Limit & Quota Headers are emitted on the single-check endpoint only, not on POST /v1/vat/bulk. Use summary and any per-item RATE_LIMITED entries to detect that you hit your quota mid-batch.
Behavior Notes
- De-duplication. Repeated VAT IDs in
vatIdsare collapsed to one entry, first-seen order preserved. - A requester bypasses the cache. When your account has a requester VAT configured in dashboard Settings, every item is checked live against VIES so it can carry a fresh consultation number tied to that requester — a cached result never carries one. This also means national-fallback recovery is skipped in this mode, same as the single endpoint: a VIES outage surfaces as
VIES_UNAVAILABLEfor that item instead of silently falling back. - Partial-fill on quota. If your monthly quota runs out partway through a batch, items already processed keep their results and every remaining item gets a per-item
RATE_LIMITED— the request itself still returns 200.
Error Codes
Per-item error codes use the same vocabulary as the single endpoint — full definitions in Error Handling.
| Code | Meaning |
|---|---|
| INVALID_FORMAT | This VAT ID doesn't match the expected format for its country |
| INVALID_REQUESTER | The requester VAT configured in dashboard Settings is rejected by VIES (only when a requester is set) |
| RATE_LIMITED | Your monthly quota ran out before this item |
| VIES_UNAVAILABLE | VIES (and any national fallback) was unreachable for this item |
| UPSTREAM_TIMEOUT | The upstream check for this item timed out |
| VIES_ERROR | Unexpected VIES protocol error for this item |
| INTERNAL_ERROR | Unhandled internal error while checking this item |
Code Examples
JavaScript
async function validateVatBulk(vatIds) {
const response = await fetch('https://api.vatnode.dev/v1/vat/bulk', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VATNODE_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ vatIds })
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error.message)
}
return response.json()
}
// Usage
const { results, summary } = await validateVatBulk([
'IE6388047V',
'DE143454214',
'DE12345'
])
console.log(`${summary.valid} valid, ${summary.invalid} invalid, ${summary.errors} errored`)
for (const result of results) {
if (result.error) {
console.warn(result.vatId, result.error.code, result.error.message)
} else {
console.log(result.vatId, result.valid ? 'valid' : 'invalid', result.source)
}
}Python
import requests
import os
def validate_vat_bulk(vat_ids):
response = requests.post(
'https://api.vatnode.dev/v1/vat/bulk',
headers={'Authorization': f'Bearer {os.environ["VATNODE_API_KEY"]}'},
json={'vatIds': vat_ids}
)
response.raise_for_status()
return response.json()
# Usage
data = validate_vat_bulk(['IE6388047V', 'DE143454214', 'DE12345'])
print(f"{data['summary']['valid']} valid, {data['summary']['errors']} errored")
for result in data['results']:
if result.get('error'):
print(result['vatId'], result['error']['code'])
else:
print(result['vatId'], 'valid' if result['valid'] else 'invalid', result['source'])How to Bulk-Validate a List of VAT Numbers
- Collect the VAT IDs. Gather the VAT numbers you want to check — up to 100 per request — with their two-letter country prefix, e.g. from an imported customer or supplier list.
- POST them to /v1/vat/bulk. Send the array as { "vatIds": [...] } in the request body with your live API key in the Authorization header. Set your EU VAT once in dashboard Settings to receive a consultation number per item automatically.
- Read the per-item results and the summary. The response is always 200 OK. Each entry in results carries its own valid/source/error; summary.requested, valid, invalid and errors give you the batch total at a glance.
- Handle partial failures. An item with an error field was not billed. Fix the input (INVALID_FORMAT) or retry later (VIES_UNAVAILABLE, RATE_LIMITED) — no need to resend the whole batch.
FAQ
How many VAT numbers can I validate in one request?
Up to 100 per call to POST /v1/vat/bulk. A request with fewer than 1 or more than 100 vatIds is rejected as a whole with a 400 before any item is processed.
Does bulk validation cost more than checking one VAT number at a time?
No. Each unique VAT ID in the batch counts as exactly one validation against your monthly quota, the same as GET /v1/vat/:vatId. There is no per-batch surcharge.
What happens if I hit my monthly quota partway through a batch?
Items already processed keep their results. Every remaining item in that batch gets a per-item RATE_LIMITED error instead — the response is still 200 OK, so you can retry just the rate-limited items after upgrading or after the quota resets.
Are duplicate VAT numbers in the same request billed twice?
No. vatIds is de-duplicated before processing (first-seen order preserved), so a repeated VAT ID appears once in results and is billed once.
See Also
- VAT Validation — the single-check endpoint, with full company enrichment and consultation-number details
- VIES API — what the underlying EU VIES register is and how vatnode wraps it
- Documentation — full API reference
- Pricing — monthly quotas and overage rates by plan