Error Codes

All API errors follow a consistent format. The HTTP status code indicates the category of error, and the response body contains a machine-readable error code and a human-readable message.

Error format

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Company not found for KVK number 99999999"
  }
}

Error codes

Code HTTP Status Description
NOT_FOUND 404 No company found for the given KVK or VAT number.
INVALID_KVK_NUMBER 400 The provided KVK number is not a valid 8-digit number.
INVALID_VAT_NUMBER 400 The provided VAT number does not match the expected format (e.g., NL + 9 digits + B + 2 digits).
INVALID_QUERY 400 The search parameters were rejected. A postcode filter must be paired with a huisnummer and written without a space (6604LW, not 6604 LW). This is a problem with the query, not with the service.
UNAUTHORIZED 401 Missing or invalid API key. Ensure you're sending a valid key via the Authorization header or apikey query parameter.
RATE_LIMIT_EXCEEDED 429 You've exceeded the rate limit for your plan. Wait and try again, or upgrade your plan for higher limits.
QUOTA_EXCEEDED 429 You've used all your allocated lookups/searches/validations for this month. Upgrade your plan or wait until the next billing cycle.
UPSTREAM_ERROR 502 An upstream service (KVK API or VIES) is temporarily unavailable. The request may succeed if retried. On /v1/validate/vat this is returned only when we have nothing useful to say at all, see VAT validation during a VIES outage.
INTERNAL_ERROR 500 An unexpected error occurred. If this persists, please contact support.

VAT validation during a VIES outage

VIES is a gateway to each member state's own VAT service, and those services go down. When a member state is unavailable, VIES answers with HTTP 200 and no verdict at all. We never turn that into "valid": false, because a customer would read it as "this VAT number is invalid" and reject a perfectly good counterparty.

Instead, /v1/validate/vat/{vatNumber} tells you which of four situations you are in through a status field. Three of them are a 200, and only the last is a 502.

status HTTP What it means
validated 200 VIES answered, just now or from a cache entry that has not expired. valid is VIES's own verdict.
stale 200 VIES could not be reached, so we are serving a VIES verdict we obtained earlier, past its normal cache lifetime. validatedAt and ageSeconds tell you how old it is. We serve these for up to 30 days, then stop. Treat it as evidence, not as a live confirmation.
malformed 200 VIES could not be reached, but the number fails the Dutch elfproef, so it cannot be a real Dutch VAT number. valid is false and you can act on it.
(none) 502 UPSTREAM_ERROR. No cached verdict within 30 days and the checksum cannot settle it, so we have nothing. Retry later. This is not a verdict on the number.

Every response also carries checksumValid, the result of the Dutch elfproef over the nine digit block. It is true when the number is well formed, which is not the same as valid: the check rules out typos and transposed digits, and a random nine digit number still passes about one time in eleven. It says nothing about whether the number is registered or active. Only VIES answers that. For a non Dutch number it is null, because every member state runs its own scheme.

{
  "vatNumber": "NL123456789B01",
  "valid": true,
  "name": "Acme B.V.",
  "address": "Herengracht 100, 1015 AA Amsterdam",
  "validatedAt": "2026-08-14T09:12:00Z",
  "status": "stale",
  "ageSeconds": 786240,
  "checksumValid": true,
  "unavailableReason": "MS_UNAVAILABLE"
}

The same status, checksumValid and ageSeconds fields appear on the vat object of a company lookup, plus a fourth value, unvalidated, for a derived VAT candidate that VIES has never confirmed. There valid is null.

Handling errors

We recommend checking the HTTP status code first, then parsing the error body for more details:

const res = await fetch("https://api.kvkbase.nl/v1/lookup/12345678", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

if (!res.ok) {
  const { error } = await res.json();

  switch (error.code) {
    case "NOT_FOUND":
      console.log("Company not found");
      break;
    case "RATE_LIMIT_EXCEEDED":
      // Wait and retry
      const retryAfter = res.headers.get("Retry-After");
      break;
    case "QUOTA_EXCEEDED":
      console.log("Monthly quota reached");
      break;
    default:
      console.error("API error:", error.message);
  }
}

Rate limit headers

Every response includes rate limit information in the headers:

Header Description
X-RateLimit-Limit Maximum requests per minute for your plan
X-RateLimit-Remaining Requests remaining in the current window
X-RateLimit-Reset Unix timestamp when the rate limit window resets
Retry-After Seconds to wait before retrying (only on 429 responses)