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/bulk

Requires a live API key, the same as every other billable endpoint — see Authentication.

Request

Body

FieldTypeRequiredDescription
vatIdsstring[]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.

Request
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.

Response
{
  "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

FieldTypeDescription
resultsarrayOne entry per unique VAT ID submitted, in first-seen order.
results[].vatIdstringThe normalized VAT number
results[].validboolean | nulltrue/false on a completed check; null when error is set — no determination was made.
results[].sourcestring | nullVIES, CACHE, or a national fallback code — same meaning as on the single endpoint. Null when the item errored.
results[].consultationNumberstring | nullPresent 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[].errorobject (optional)Present only when this item failed. Carries code and message — see Error Codes.
summary.requestednumberNumber of unique VAT IDs processed — results.length. Duplicates in the submitted array are collapsed before this count.
summary.validnumberItems with valid: true
summary.invalidnumberItems with valid: false
summary.errorsnumberItems 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 vatIds are 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_UNAVAILABLE for 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.

CodeMeaning
INVALID_FORMATThis VAT ID doesn't match the expected format for its country
INVALID_REQUESTERThe requester VAT configured in dashboard Settings is rejected by VIES (only when a requester is set)
RATE_LIMITEDYour monthly quota ran out before this item
VIES_UNAVAILABLEVIES (and any national fallback) was unreachable for this item
UPSTREAM_TIMEOUTThe upstream check for this item timed out
VIES_ERRORUnexpected VIES protocol error for this item
INTERNAL_ERRORUnhandled internal error while checking this item

Code Examples

JavaScript

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

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

  1. 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.
  2. 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.
  3. 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.
  4. 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