KVKBase Team

Belgische bedrijfs-API: codevoorbeelden in curl, JavaScript en Python

Werkende codevoorbeelden voor de Belgische bedrijfsgegevens-API: lookup, zoeken op naam en postcode, btw-controle, foutafhandeling en rate limits.

belgieapicodedevelopers

Een API leer je het snelst kennen met code die meteen werkt. Deze gids geeft voorbeelden in curl, JavaScript (fetch, Node 18 of nieuwer) en Python (requests) voor de vier dingen die je het vaakst nodig hebt: een bedrijf opzoeken met het ondernemingsnummer, zoeken op naam en postcode, een btw-nummer controleren en netjes omgaan met fouten en rate limits. De algemene opzet van een integratie staat in de gids over het integreren van de API, hier ligt de nadruk op copy-paste.

Voorbereiding

De basis-URL is https://api.kvkbase.nl. Elke aanvraag draagt de header Authorization: Bearer YOUR_API_KEY. Je sleutel maak je aan na je registratie, het gratis plan bevat 50 tokens per maand en vraagt geen kredietkaart. Prijzen en grotere plannen vind je op de prijspagina.

In alle voorbeelden hieronder lezen we de sleutel uit een omgevingsvariabele KVKBASE_API_KEY. Het ondernemingsnummer 0123.456.789 is een illustratief voorbeeld, vervang het door een nummer dat je kent. Elke endpoint is beschreven in de API-referentie.

Een bedrijf opzoeken met het ondernemingsnummer

Het nummer mag met of zonder punten en met of zonder BE-prefix. De optionele parameter lang (nl, fr, de of en) bepaalt de taal van labels zoals de rechtsvorm.

curl -s "https://api.kvkbase.nl/v1/be/lookup/0123.456.789?lang=nl" \
  -H "Authorization: Bearer YOUR_API_KEY"

In JavaScript schrijf je een kleine functie die bij een fout een duidelijke uitzondering gooit. Zo kan de code die haar aanroept de statuscode en de foutcode lezen.

const BASE = "https://api.kvkbase.nl";
const KEY = process.env.KVKBASE_API_KEY;

async function api(path, init = {}) {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${KEY}`, ...init.headers },
    signal: AbortSignal.timeout(10_000),
  });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw Object.assign(new Error(body?.error?.message ?? `HTTP ${res.status}`), {
      status: res.status,
      code: body?.error?.code,
      retryAfter: Number(res.headers.get("Retry-After")) || null,
    });
  }
  return res.json();
}

const company = await api("/v1/be/lookup/0123.456.789?lang=nl");
console.log(company.name, company.legalFormShort, company.isActive);

Dezelfde aanroep in Python met requests:

import os
import requests

BASE = "https://api.kvkbase.nl"
SESSION = requests.Session()
SESSION.headers["Authorization"] = f"Bearer {os.environ['KVKBASE_API_KEY']}"

def api(path, **kwargs):
    r = SESSION.get(f"{BASE}{path}", timeout=10, **kwargs)
    r.raise_for_status()
    return r.json()

company = api("/v1/be/lookup/0123.456.789", params={"lang": "nl"})
print(company["name"], company["legalFormShort"], company["isActive"])

Het antwoord bevat onder meer cbeNumber, cbeNumberFormatted, name, tradingNames, legalForm, legalFormShort, entityType, address, isActive, juridicalSituation, activities, establishments en vat. Het blok source toont met cachedAt en stale hoe vers het antwoord is.

Zoeken op naam en postcode

Heb je geen nummer, dan gebruik je GET /v1/be/search. Je geeft een naam in q (minstens twee tekens), een postalCode van vier cijfers, of allebei. Allebei geeft de scherpste resultaten.

curl -s "https://api.kvkbase.nl/v1/be/search?q=bakkerij&postalCode=9000&pageSize=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({ q: "bakkerij", postalCode: "9000", pageSize: "10" });
const { results, total, capped } = await api(`/v1/be/search?${params}`);

for (const hit of results) {
  console.log(hit.cbeNumberFormatted, hit.name, hit.postalCode, hit.city);
}
if (capped) console.log("Mogelijk meer treffers: verfijn de zoekopdracht.");
data = api("/v1/be/search", params={"q": "bakkerij", "postalCode": "9000", "pageSize": 10})
for hit in data["results"]:
    print(hit["cbeNumberFormatted"], hit["name"], hit["postalCode"], hit["city"])
if data["capped"]:
    print("Mogelijk meer treffers: verfijn de zoekopdracht.")

Een zoekopdracht geeft nooit meer dan 25 treffers terug, ook niet als je page ophoogt. De parameters page en pageSize knippen alleen in diezelfde 25. Waar capped is true, bestaan er mogelijk meer bedrijven en moet je verfijnen. Meer over dat patroon staat in de gids over zoeken op postcode en sector.

Een btw-nummer controleren

Voor de btw-controle roep je GET /v1/be/validate/vat/{btw-nummer} aan. Een Belgisch btw-nummer is BE gevolgd door het ondernemingsnummer, zonder punten.

curl -s "https://api.kvkbase.nl/v1/be/validate/vat/BE0123456789" \
  -H "Authorization: Bearer YOUR_API_KEY"
const vat = await api("/v1/be/validate/vat/BE0123456789");
if (vat.status === "validated") {
  console.log(vat.valid ? "Btw-nummer actief" : "Btw-nummer niet geldig", vat.name);
} else if (vat.status === "stale") {
  console.log("Oud VIES-antwoord van", vat.validatedAt, "(VIES was niet bereikbaar)");
} else {
  console.log("Foutief formaat of controlegetal");
}

Het antwoord bevat vatNumber, valid, name, address, validatedAt, status en checksumValid. De waarde validated betekent dat VIES het antwoord gaf. Is VIES even onbereikbaar, dan kan status stale zijn, een eerder opgeslagen antwoord met datum, en bij een nummer dat het controlegetal niet haalt is het malformed. Behandel stale nooit als een verse bevestiging. Hoe het controlegetal werkt lees je in de gids over de opbouw van het btw-nummer.

Foutafhandeling

Elke fout heeft dezelfde vorm: {"error": {"code": "...", "message": "..."}}. Beslis op basis van de statuscode en de code.

CodeStatusWat doen
INVALID_CBE_NUMBER400Niet herhalen, laat de gebruiker het nummer corrigeren
INVALID_QUERY400Zoekopdracht aanpassen (naam te kort, postcode geen vier cijfers)
NOT_FOUND404Niet herhalen, het nummer bestaat niet in de gegevens
RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED429Zie de volgende sectie
UPSTREAM_ERROR502Beperkt herhalen met backoff
UPSTREAM_BUDGET503Wachten tot Retry-After en later opnieuw

Belangrijk onderscheid: een 404 betekent dat het nummer niet bestaat, een 502 of 503 betekent dat we het even niet konden nagaan. Verwar die twee niet, anders markeer je een bestaand bedrijf als onbekend.

Rate limits en herhaalpogingen

Een 429 kan twee oorzaken hebben. Bij een rate limit zit je te snel: het antwoord bevat Retry-After in seconden en de headers X-RateLimit-Limit en X-RateLimit-Remaining tonen je marge. Bij een opgebruikt maandquotum heeft wachten geen zin, dan schaal je op of wacht je tot de volgende maand.

async function apiWithRetry(path, attempts = 4) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await api(path);
    } catch (e) {
      const retryable = [429, 502, 503].includes(e.status) && e.code !== "QUOTA_EXCEEDED";
      if (!retryable || i === attempts - 1) throw e;
      const wait = e.retryAfter ?? 2 ** i;
      await new Promise((r) => setTimeout(r, wait * 1000));
    }
  }
}
import time

def api_with_retry(path, attempts=4, **kwargs):
    for i in range(attempts):
        try:
            return api(path, **kwargs)
        except requests.HTTPError as e:
            r = e.response
            code = (r.json().get("error") or {}).get("code") if r.content else None
            retryable = r.status_code in (429, 502, 503) and code != "QUOTA_EXCEEDED"
            if not retryable or i == attempts - 1:
                raise
            time.sleep(int(r.headers.get("Retry-After", 2 ** i)))

Houd een eigen teller bij als je veel oproepen doet, en lees X-RateLimit-Remaining om vooraf af te remmen in plaats van achteraf te herstellen.

Tips voor productie

  • Zet altijd een time-out op je oproepen.
  • Valideer ondernemingsnummers lokaal voor je belt, zie de gids over het controlegetal.
  • Bewaar antwoorden met een eigen tijdstip, zodat je niet voor elke weergave opnieuw betaalt.
  • Toon natuurlijke personen (eenmanszaken) niet zomaar publiek, hun adres kan privé zijn.

Verder lezen

Wil je een hele lijst nummers verwerken, lees dan de gids over bedrijven in bulk controleren. Een overzicht van het product staat op de Belgische landingspagina, en je kan de werking eerst proberen met de gratis tool om een ondernemingsnummer op te zoeken. Over het verschil tussen de nummers lees je meer in de gids ondernemingsnummer en btw-nummer.

Veelgestelde vragen

Welke header gebruik ik voor authenticatie?
Stuur bij elke aanvraag de header Authorization: Bearer YOUR_API_KEY mee naar https://api.kvkbase.nl. Bewaar de sleutel op de server, bijvoorbeeld in een omgevingsvariabele, en nooit in code die in de browser draait.
Wat doe ik bij een 429-antwoord?
Lees de header Retry-After, wacht zoveel seconden en probeer opnieuw. Let wel op de foutcode in de body: RATE_LIMIT_EXCEEDED lost zich binnen een minuut op, QUOTA_EXCEEDED betekent dat je maandelijkse tokens op zijn en wachten niet helpt.
Hoeveel resultaten geeft de zoekopdracht terug?
Maximaal 25 per oproep. Het antwoord bevat het veld capped. Is dat waar, dan zijn er mogelijk meer treffers en moet je de zoekopdracht verfijnen, bijvoorbeeld met een postcode.
Kosten de voorbeelden uit deze gids tokens?
Een geslaagde lookup, zoekopdracht of btw-controle kost 1 token. Een ongeldig ondernemingsnummer wordt geweigerd met INVALID_CBE_NUMBER (400) voor er opgezocht wordt, en kost dus niets.