API Reference
Base URL https://api.kvkbase.nl/v1
Authentication
All endpoints (except /v1/health) require an API key. Two methods are supported:
Bearer Token
Authorization: Bearer YOUR_API_KEY Query Parameter
?apikey=YOUR_API_KEY Get your API key from the Dashboard. Keys are scoped to your account tier and rate limits.
/v1/lookup/{kvkNumber} Company Lookup
Look up detailed company information by KVK number. Returns company name, address, legal form, activities, and VIES-validated VAT number in a single call. The vat object carries a status of validated, stale, malformed or unvalidated, so a VIES outage never reaches you as a false negative. See the Error Codes page for what each one means.
Parameters
kvkNumber required vat Request
curl -X GET "https://api.kvkbase.nl/v1/lookup/12345678" \
-H "Authorization: Bearer YOUR_API_KEY" const response = await fetch(
"https://api.kvkbase.nl/v1/lookup/12345678",
{
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
}
);
const company = await response.json();
console.log(company.name); // "Acme B.V." import requests
response = requests.get(
"https://api.kvkbase.nl/v1/lookup/12345678",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
company = response.json()
print(company["name"]) # "Acme B.V." $ch = curl_init("https://api.kvkbase.nl/v1/lookup/12345678");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch));
echo $response->name; // "Acme B.V." req, _ := http.NewRequest("GET",
"https://api.kvkbase.nl/v1/lookup/12345678", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var company map[string]interface{}
json.NewDecoder(resp.Body).Decode(&company)
fmt.Println(company["name"]) // "Acme B.V." require "net/http"
require "json"
uri = URI("https://api.kvkbase.nl/v1/lookup/12345678")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
company = JSON.parse(res.body)
puts company["name"] # "Acme B.V." Response
{
"kvkNumber": "12345678",
"name": "Acme B.V.",
"tradingNames": ["Acme", "Acme Trading"],
"legalForm": "BV",
"address": {
"street": "Herengracht",
"houseNumber": "100",
"postalCode": "1015 AA",
"city": "Amsterdam",
"country": "NL"
},
"isActive": true,
"registrationDate": "2020-01-15",
"activities": [
{ "sbiCode": "6201", "description": "Ontwikkelen en uitgeven van software", "isMain": true }
],
"vat": {
"number": "NL123456789B01",
"valid": true,
"status": "validated",
"validatedAt": "2026-03-15T12:00:00Z",
"checksumValid": true
},
"source": {
"kvk": "opendata+zoeken",
"vies": "validated",
"cachedAt": "2026-03-15T12:00:00Z"
}
} /v1/search Company Search
Search for companies by name or trade name. Returns paginated results with basic company information.
Parameters
q required page pageSize Request
curl -X GET "https://api.kvkbase.nl/v1/search?q=acme&pageSize=5" \
-H "Authorization: Bearer YOUR_API_KEY" const params = new URLSearchParams({ q: "acme", pageSize: "5" });
const response = await fetch(
`https://api.kvkbase.nl/v1/search?${params}`,
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const { results, total } = await response.json(); response = requests.get(
"https://api.kvkbase.nl/v1/search",
params={"q": "acme", "pageSize": 5},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
for company in data["results"]:
print(company["name"]) $url = "https://api.kvkbase.nl/v1/search?" . http_build_query([
"q" => "acme", "pageSize" => 5
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch)); req, _ := http.NewRequest("GET",
"https://api.kvkbase.nl/v1/search?q=acme&pageSize=5", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req) uri = URI("https://api.kvkbase.nl/v1/search")
uri.query = URI.encode_www_form(q: "acme", pageSize: 5)
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY" Response
{
"results": [
{
"kvkNumber": "12345678",
"name": "Acme B.V.",
"city": "Amsterdam",
"isActive": true
}
],
"total": 42,
"page": 1,
"pageSize": 10
} /v1/validate/vat/{vatNumber} VAT Validation
Validate a Dutch or EU VAT number against the VIES system. Returns validation status and registered company details. When a member state service is down, status tells you whether you are looking at a live verdict (validated), a dated one we obtained earlier (stale, with ageSeconds), or a number the Dutch checksum rejects outright (malformed). A 502 UPSTREAM_ERROR is reserved for when we have none of those. checksumValid is the Dutch elfproef: it means well formed, never valid.
Parameters
vatNumber required Request
curl -X GET "https://api.kvkbase.nl/v1/validate/vat/NL123456789B01" \
-H "Authorization: Bearer YOUR_API_KEY" const response = await fetch(
"https://api.kvkbase.nl/v1/validate/vat/NL123456789B01",
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const { valid, name } = await response.json(); response = requests.get(
"https://api.kvkbase.nl/v1/validate/vat/NL123456789B01",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
result = response.json()
print(f"Valid: {result['valid']}") $ch = curl_init(
"https://api.kvkbase.nl/v1/validate/vat/NL123456789B01"
);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = json_decode(curl_exec($ch)); req, _ := http.NewRequest("GET",
"https://api.kvkbase.nl/v1/validate/vat/NL123456789B01", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req) uri = URI("https://api.kvkbase.nl/v1/validate/vat/NL123456789B01")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer YOUR_API_KEY" Response
{
"vatNumber": "NL123456789B01",
"valid": true,
"name": "Acme B.V.",
"address": "Herengracht 100, 1015 AA Amsterdam",
"validatedAt": "2026-03-15T12:00:00Z",
"status": "validated",
"checksumValid": true
} /v1/be/lookup/{cbeNumber} Belgium: Company Lookup
Look up a Belgian company by enterprise number (ondernemingsnummer). Any common notation works: 0821.015.819, BE0821015819 or 821015819. The same lookup is also reachable as GET /v1/lookup/{number}. Returns name, trade names, legal form, address, status, NACE activities (in the sbiCode field, with the version in naceVersion), establishments and the VAT status in one call. The vat object works like the Dutch one: validated is a verdict from VIES or its cache, stale is an older verdict from when VIES was down, unvalidated means we have no verdict and valid is null. A verdict we do not have is never reported as false. Answers can be up to a week old. When we cannot refresh a record, for example because our data allowance for the day is used up, we serve the stored copy and set source.stale to true. Costs 1 token.
Parameters
cbeNumber required lang Request
curl "https://api.kvkbase.nl/v1/be/lookup/0821.015.819?lang=nl" \
-H "Authorization: Bearer YOUR_API_KEY" const response = await fetch(
"https://api.kvkbase.nl/v1/be/lookup/BE0821015819",
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const company = await response.json();
console.log(company.name, company.vat?.status); // "INSPACE" "validated" Response
{
"country": "BE",
"cbeNumber": "0821015819",
"cbeNumberFormatted": "0821.015.819",
"name": "INSPACE",
"tradingNames": [],
"legalForm": "Besloten vennootschap",
"legalFormCode": "610",
"legalFormShort": "BV",
"entityType": "legal_person",
"address": {
"street": "Rue des Deux Chaussees",
"houseNumber": "25",
"postalCode": "1160",
"city": "Auderghem",
"country": "BE"
},
"isActive": true,
"juridicalSituation": "Normale toestand",
"juridicalSituationCode": "000",
"registrationDate": "2009-12-01",
"activities": [
{ "sbiCode": "43990", "description": "Overige gespecialiseerde bouwactiviteiten", "isMain": true },
{ "sbiCode": "64200", "description": "Holdings", "isMain": false }
],
"naceVersion": "Nace2025",
"establishments": [
{
"establishmentNumber": "2183353402",
"startDate": "2009-12-18",
"address": { "street": "Rue des Deux Chaussees", "houseNumber": "25", "postalCode": "1160", "city": "Auderghem", "country": "BE" }
}
],
"vat": {
"number": "BE0821015819",
"valid": true,
"status": "validated",
"validatedAt": "2026-11-09T09:30:00Z",
"checksumValid": true
},
"enriched": false,
"source": {
"cbe": "register",
"vies": "live",
"cachedAt": "2026-11-09T09:30:00Z"
}
} /v1/be/search Belgium: Company Search
Search Belgian companies by name, postal code or both. Returns at most 25 companies per query. capped is true when the limit was reached, so narrow the query with a postal code. Page and pageSize only slice that list. Results come with the legal form, status and place, use the lookup for the full record. Search answers can be up to an hour old. When our data allowance for the day is used up we serve a stored copy of the same query and set stale to true. Costs 1 token per request.
Parameters
q postalCode page pageSize lang Request
curl "https://api.kvkbase.nl/v1/be/search?q=colruyt&postalCode=1500" \
-H "Authorization: Bearer YOUR_API_KEY" const params = new URLSearchParams({ q: "colruyt", postalCode: "1500" });
const response = await fetch(
`https://api.kvkbase.nl/v1/be/search?${params}`,
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const { results, total, capped } = await response.json(); Response
{
"results": [
{
"cbeNumber": "0400378485",
"cbeNumberFormatted": "0400.378.485",
"name": "Colruyt Group",
"entityType": "legal_person",
"legalForm": "Naamloze vennootschap",
"legalFormShort": "NV",
"postalCode": "1500",
"city": "Halle",
"country": "BE",
"isActive": true
}
],
"total": 1,
"page": 1,
"pageSize": 10,
"capped": false
} /v1/be/lookup/batch Belgium: Batch Lookup
Look up up to 25 Belgian companies in one request. Costs 1 token per enterprise number, charged up front. The request fails with 400 INVALID_CBE_NUMBER when any number is invalid. Per company, the result is either success with the same data as a single lookup, or an error object, so one missing company does not fail the batch. A number that appears twice is looked up once.
Parameters
cbeNumbers required lang Request
curl -X POST "https://api.kvkbase.nl/v1/be/lookup/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cbeNumbers": ["0821.015.819", "BE0417497106"]}' const response = await fetch(
"https://api.kvkbase.nl/v1/be/lookup/batch",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
cbeNumbers: ["0821.015.819", "BE0417497106"],
}),
}
);
const { results, count, tokensUsed } = await response.json(); Response
{
"results": {
"0821015819": { "success": true, "data": { "country": "BE", "cbeNumber": "0821015819", "name": "INSPACE", "...": "same fields as a single lookup" } },
"0417497106": { "success": false, "error": { "error": { "code": "NOT_FOUND", "message": "Company with enterprise number 0417497106 not found." } } }
},
"count": 2,
"tokensUsed": 2
} /v1/be/validate/vat/{vatNumber} Belgium: VAT Validation
Validate a Belgian VAT number (BE plus the 10 digit enterprise number, a 9 digit number is accepted too) against VIES. The path with the BE prefix, /v1/validate/vat/BE0821015819, works the same. A number that fails the Belgian check digits is answered as malformed without calling VIES. Status values and the checksumValid field have the same meaning as for Dutch VAT numbers: validated, stale, malformed, and 502 UPSTREAM_ERROR when we have nothing at all.
Parameters
vatNumber required Request
curl "https://api.kvkbase.nl/v1/be/validate/vat/BE0821015819" \
-H "Authorization: Bearer YOUR_API_KEY" const response = await fetch(
"https://api.kvkbase.nl/v1/be/validate/vat/BE0821015819",
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const { valid, status } = await response.json(); Response
{
"vatNumber": "BE0821015819",
"valid": true,
"validatedAt": "2026-11-09T09:30:00Z",
"status": "validated",
"checksumValid": true,
"country": "BE"
} /v1/health Health Check
Check API and upstream service status. No authentication required.
Request
curl https://api.kvkbase.nl/v1/health const response = await fetch("https://api.kvkbase.nl/v1/health");
const health = await response.json();
console.log(health.status); // "ok" response = requests.get("https://api.kvkbase.nl/v1/health")
print(response.json()["status"]) # "ok" $health = json_decode(
file_get_contents("https://api.kvkbase.nl/v1/health")
); resp, _ := http.Get("https://api.kvkbase.nl/v1/health") res = Net::HTTP.get(URI("https://api.kvkbase.nl/v1/health")) Response
{
"status": "ok",
"services": {
"kvk": "up",
"vies": "up"
},
"timestamp": "2026-03-15T12:00:00Z"
} Belgium notes
The Belgian endpoints above return the same kind of data as the Dutch ones, with these differences.
0821.015.819, BE0821015819 and 821015819 are the same company. cbeNumber has no separators, cbeNumberFormatted has dots. An invalid number is answered with 400 INVALID_CBE_NUMBER before any token is charged.country is always BE. legalForm is the label in the requested language, legalFormCode the register code, legalFormShort the abbreviation (BV, NV and so on). entityType is legal_person or natural_person. isActive is true, false or null when unknown. activities carry NACE codes in sbiCode, naceVersion says which NACE version the codes use.lang parameter takes nl, fr or de and decides the language of legal form, juridical situation and activity descriptions. en is accepted and served in Dutch.source.stale to true (stale on a search). Check source.cachedAt for the age.vat object has the same semantics as for Dutch companies: validated, stale, malformed or unvalidated, with valid: null when we have no verdict. A VIES outage never shows up as a false negative.INVALID_CBE_NUMBER (400) for a malformed number or a failing check digit pair. UPSTREAM_BUDGET (503, with Retry-After) when our data allowance for the day is used up and the record is not stored yet: retry after the number of seconds in the header. NOT_FOUND (404) for an unknown number. See the Error Codes page.Errors
All errors return a consistent JSON format:
{
"error": {
"code": "NOT_FOUND",
"message": "No company found with KVK number 99999999"
}
} See the Error Codes page for a complete reference with HTTP status codes.
Rate Limits
Rate limits are applied per API key based on your plan tier:
freestarterprobusinessToken Costs
?enrich=true)?enrich=true&vat=true)When rate limited, the API returns 429 Too Many Requests with a Retry-After header.