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 response format

Every error response has this structure:

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

Error code reference

All possible error codes, their HTTP status, and common causes:

NOT_FOUND 404

No company found for the given identifier.

Common cause: The KVK number, VAT number, or search query returned no results. Verify the number is correct and that the company is registered.
INVALID_KVK_NUMBER 400

The provided KVK number is not valid.

Common cause: KVK numbers must be exactly 8 digits. Check for extra spaces, dashes, or letters in the input.
INVALID_VAT_NUMBER 400

The provided VAT number format is not recognized.

Common cause: Dutch VAT numbers follow the format NL + 9 digits + B + 2 digits (e.g. NL123456789B01). EU VAT numbers must start with a valid country code.
INVALID_QUERY 400

The search parameters were rejected.

Common cause: A postcode filter must be paired with a huisnummer and must be written without a space (6604LW, not 6604 LW). Anything else KVK rejects for this search comes back under this code too. This is a problem with the query, not with the service.
INVALID_REQUEST 400

The request is missing required parameters or has invalid values.

Common cause: Check required fields: search needs a q parameter with at least 2 characters, batch needs an array of KVK numbers, etc.
UNAUTHORIZED 401

Missing or invalid API key.

Common cause: Ensure you are sending a valid key via the Authorization header (Bearer token) or apikey query parameter. Keys start with kvk_live_ or kvk_test_.
RATE_LIMIT_EXCEEDED 429

Too many requests in the current time window.

Common cause: You have exceeded the per-minute rate limit for your plan. Check the Retry-After header and wait before retrying. Consider upgrading for higher limits.
QUOTA_EXCEEDED 429

Monthly token allowance exhausted.

Common cause: You have used all tokens for this billing cycle. Upgrade your plan or wait until the next month. Check X-Tokens-Remaining header to monitor usage.
UPSTREAM_ERROR 502

An upstream service is temporarily unavailable.

Common cause: The KVK API or VIES service is not responding. This is usually temporary. Retry after a short delay. Check /v1/health for service status. On /v1/validate/vat this is returned only when we have nothing useful to say at all: see VAT validation during a VIES outage below.
INTERNAL_ERROR 500

An unexpected server error occurred.

Common cause: This should not happen in normal operation. If it persists, contact support with the request ID from the response headers.

Handling errors

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

javascript
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":
      const retryAfter = res.headers.get("Retry-After");
      console.log(`Rate limited. Retry after ${retryAfter}s`);
      break;
    case "QUOTA_EXCEEDED":
      console.log("Monthly token quota reached");
      break;
    case "UNAUTHORIZED":
      console.log("Invalid API key");
      break;
    default:
      console.error("API error:", error.message);
  }
}

Rate limit headers

Every API response includes headers with rate limit and token usage information. Use these to monitor your usage and avoid hitting limits.

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

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.

json
{
  "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.

Retry strategy

For production applications, we recommend implementing exponential backoff:

javascript
async function kvkLookup(kvkNumber, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(
      `https://api.kvkbase.nl/v1/lookup/${kvkNumber}`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } }
    );

    if (res.ok) return res.json();

    if (res.status === 429) {
      const wait = parseInt(res.headers.get("Retry-After") || "5");
      await new Promise(r => setTimeout(r, wait * 1000));
      continue;
    }

    if (res.status === 502) {
      // Upstream error -- wait and retry
      await new Promise(r => setTimeout(r, (attempt + 1) * 2000));
      continue;
    }

    // Non-retryable error
    const { error } = await res.json();
    throw new Error(error.message);
  }
  throw new Error("Max retries exceeded");
}