Batch Validation
Validate up to 50 VAT numbers in one request. Available on Pro and Business tiers only.
Each VAT number is validated independently against VIES (EU) or HMRC (UK). Results are returned in the same order as the input array.
Request body
| Field | Type | Required | Description |
|---|
vat_numbers | string[] | Yes | Array of VAT numbers to validate (1-50 items) |
requester_vat_number | string | No | Your own VAT number, to receive consultation numbers |
cache | boolean | No | Set to false to bypass the 24-hour cache. Defaults to true |
Response shape
The response always returns HTTP 200 with a data object containing results and summary. Per-item errors appear inline in the results array rather than causing the entire request to fail.
Success result
{
"data": {
"valid": true,
"vat_number": "NL123456789B01",
"country_code": "NL",
"company": {
"name": "Acme B.V.",
"address": "Keizersgracht 123, Amsterdam"
},
"requested_at": "2026-03-18T12:00:00Z"
},
"meta": {
"cached": true,
"cached_at": "2026-03-18T10:00:00Z"
}
}
The data object is the same VatValidationData shape as the single validate endpoint. Per-item meta contains cache information for that specific result.
Error result
When an individual VAT number fails (e.g. upstream timeout, invalid format), its result contains an error object instead of data. The vat_number that failed is in meta.
{
"error": {
"code": "upstream_unavailable",
"message": "HMRC service unavailable"
},
"meta": {
"vat_number": "XX000000000"
}
}
Summary
Every batch response includes a summary object:
{
"total": 3,
"succeeded": 2,
"failed": 1
}
Full example response
{
"data": {
"results": [
{
"data": {
"valid": true,
"vat_number": "NL123456789B01",
"country_code": "NL",
"company": {
"name": "Acme B.V.",
"address": "Keizersgracht 123, Amsterdam"
},
"requested_at": "2026-03-18T12:00:00Z"
},
"meta": {
"cached": true,
"cached_at": "2026-03-18T10:00:00Z"
}
},
{
"data": {
"valid": true,
"vat_number": "DE987654321",
"country_code": "DE",
"company": {
"name": "Beispiel GmbH",
"address": "Berliner Str. 42, Berlin"
},
"requested_at": "2026-03-18T12:00:01Z"
},
"meta": {}
},
{
"error": {
"code": "upstream_unavailable",
"message": "VIES service unavailable"
},
"meta": {
"vat_number": "FR12345678901"
}
}
],
"summary": {
"total": 3,
"succeeded": 2,
"failed": 1
}
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"request_duration_ms": 850
}
}
Deduplication
Duplicate VAT numbers in the input array are deduplicated internally. Only unique numbers are validated against the upstream service and counted toward your monthly quota. The response still returns one result per input item in the original order — duplicates receive the same result.
For example, sending ["NL123456789B01", "NL123456789B01", "DE987654321"] validates 2 unique numbers and returns 3 results.
Examples
curl -X POST https://api.vatly.dev/v1/validate/batch \
-H "Authorization: Bearer vtly_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"vat_numbers": ["NL123456789B01", "DE987654321", "GB123456789"],
"requester_vat_number": "FR99999999999"
}'
Error handling
Batch validation has two levels of errors:
HTTP-level errors
These prevent the entire batch from executing. The response shape is the standard { error, meta } envelope.
| HTTP Status | Error Code | Cause |
|---|
| 401 | unauthorized | Invalid or missing API key |
| 403 | tier_insufficient | Free tier — upgrade to Pro or Business |
| 422 | validation_error | Invalid request body (e.g. empty array, >50 items) |
| 429 | rate_limit_exceeded | Monthly quota insufficient for this batch |
Inline errors
When the batch executes but individual VAT numbers fail, those failures appear as inline errors in the results array. The HTTP status is still 200 and the summary.failed count tells you how many items had errors.
Common inline error codes: invalid_vat_format, upstream_unavailable.
Always check both levels: first result.error for HTTP-level failures, then iterate result.data.results for inline per-item errors.