KVKBase Team

API d'entreprises belges : exemples en curl, JavaScript et Python

Exemples de code fonctionnels pour l'API d'entreprises belges : fiche par numéro, recherche par nom et code postal, TVA, gestion des erreurs et des quotas.

belgiqueapicodedéveloppeur

Pour prendre en main une API, rien ne vaut quelques exemples que l’on peut exécuter tout de suite. Vous trouverez ici du code en curl, en JavaScript (fetch, Node 18 ou plus récent) et en Python (requests) pour les quatre opérations les plus courantes : obtenir la fiche d’une entreprise à partir de son numéro, chercher par nom et code postal, contrôler un numéro de TVA, puis gérer proprement les erreurs et les limites de débit. Pour la vue d’ensemble d’une intégration, consultez d’abord le guide d’intégration de l’API.

Avant de commencer

Tous les appels visent https://api.kvkbase.nl et portent l’en-tête Authorization: Bearer YOUR_API_KEY. Vous obtenez votre clé en créant un compte : le plan gratuit comprend 50 tokens par mois, sans carte bancaire. Les plans supérieurs sont détaillés sur la page des tarifs.

Dans les exemples, la clé est lue dans la variable d’environnement KVKBASE_API_KEY. Le numéro 0123.456.789 est purement illustratif : remplacez-le par un numéro que vous connaissez. La liste complète des champs se trouve dans la référence de l’API.

Obtenir une fiche à partir du numéro d’entreprise

Le numéro peut s’écrire avec ou sans points, avec ou sans préfixe BE. Le paramètre facultatif lang (nl, fr, de ou en) choisit la langue des libellés, comme la forme juridique. Pour un affichage francophone, passez lang=fr.

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

En JavaScript, une petite fonction d’aide suffit. Elle lève une exception qui porte le statut HTTP, le code d’erreur et le délai d’attente éventuel, ce qui simplifie le code appelant.

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 entreprise = await api("/v1/be/lookup/0123.456.789?lang=fr");
console.log(entreprise.name, entreprise.legalFormShort, entreprise.isActive);

L’équivalent en Python avec 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()

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

La réponse contient notamment cbeNumber, cbeNumberFormatted, name, tradingNames, legalForm, legalFormShort, entityType, address, isActive, juridicalSituation, activities, establishments et vat. Le bloc source indique, avec cachedAt et stale, l’âge de la donnée servie.

Rechercher par nom et code postal

Sans numéro, utilisez GET /v1/be/search. Passez un nom dans q (deux caractères au minimum), un postalCode de quatre chiffres, ou les deux. La combinaison donne les résultats les plus précis.

curl -s "https://api.kvkbase.nl/v1/be/search?q=boulangerie&postalCode=4000&pageSize=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({ q: "boulangerie", postalCode: "4000", pageSize: "10" });
const { results, 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("Il y a peut-être d'autres résultats : affinez la recherche.");
data = api("/v1/be/search", params={"q": "boulangerie", "postalCode": "4000", "pageSize": 10})
for hit in data["results"]:
    print(hit["cbeNumberFormatted"], hit["name"], hit["postalCode"], hit["city"])
if data["capped"]:
    print("Il y a peut-être d'autres résultats : affinez la recherche.")

Une recherche ne renvoie jamais plus de 25 résultats, même si vous augmentez page : page et pageSize ne découpent que ces 25 résultats. Quand capped vaut true, la liste est peut-être incomplète et il faut préciser les critères. Le guide sur la recherche par code postal et par secteur approfondit ce point.

Contrôler un numéro de TVA

Pour la TVA, appelez GET /v1/be/validate/vat/{numero}. Un numéro de TVA belge se compose de BE suivi du numéro d’entreprise, sans points.

curl -s "https://api.kvkbase.nl/v1/be/validate/vat/BE0123456789" \
  -H "Authorization: Bearer YOUR_API_KEY"
const tva = await api("/v1/be/validate/vat/BE0123456789");
if (tva.status === "validated") {
  console.log(tva.valid ? "Numéro de TVA actif" : "Numéro de TVA non valide", tva.name);
} else if (tva.status === "stale") {
  console.log("Ancienne réponse VIES datée du", tva.validatedAt, "(VIES injoignable)");
} else {
  console.log("Format ou clé de contrôle incorrects");
}

La réponse comprend vatNumber, valid, name, address, validatedAt, status et checksumValid. Le statut validated indique que VIES a répondu. Si VIES est momentanément injoignable, status peut valoir stale (réponse antérieure, datée) et un numéro qui échoue au contrôle de la clé reçoit malformed. Ne traitez jamais stale comme une confirmation fraîche. Pour comprendre la clé de contrôle, lisez le guide sur la structure du numéro de TVA belge.

Gérer les erreurs

Toutes les erreurs ont la même forme : {"error": {"code": "...", "message": "..."}}. Décidez à partir du statut HTTP et du code.

CodeStatutAction
INVALID_CBE_NUMBER400Ne pas réessayer, faire corriger la saisie
INVALID_QUERY400Corriger la recherche (nom trop court, code postal sans quatre chiffres)
NOT_FOUND404Ne pas réessayer, le numéro n’existe pas dans les données
RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED429Voir la section suivante
UPSTREAM_ERROR502Réessayer un nombre limité de fois, avec délai croissant
UPSTREAM_BUDGET503Attendre la durée de Retry-After, puis réessayer

La distinction essentielle : un 404 dit que le numéro n’existe pas, tandis qu’un 502 ou un 503 dit que nous n’avons pas pu vérifier à cet instant. Les confondre ferait passer une entreprise existante pour inconnue.

Limites de débit et nouvelles tentatives

Un 429 a deux causes possibles. En cas de limite de débit, vous allez trop vite : la réponse contient Retry-After en secondes, et les en-têtes X-RateLimit-Limit et X-RateLimit-Remaining indiquent votre marge. Si le quota mensuel est épuisé, attendre est inutile : il faut changer de plan ou patienter jusqu’au mois suivant.

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)))

Si vous enchaînez beaucoup d’appels, surveillez X-RateLimit-Remaining pour ralentir à temps au lieu de réagir après coup.

Bonnes pratiques en production

  • Fixez toujours un délai maximal sur chaque appel.
  • Validez les numéros localement avant d’appeler, comme l’explique le guide sur la clé de contrôle.
  • Stockez les réponses avec votre propre horodatage pour ne pas payer à chaque affichage.
  • N’exposez pas sans précaution les personnes physiques (indépendants en nom propre) : leur adresse peut être privée.

Pour aller plus loin

Pour traiter toute une liste de numéros d’un coup, lisez le guide pour vérifier des entreprises en masse. La page d’accueil belge présente le produit, et vous pouvez d’abord tester l’outil gratuit pour rechercher un numéro d’entreprise. Sur la différence entre les deux numéros, voyez le guide numéro d’entreprise et numéro de TVA.

Questions fréquentes

Quel en-tête faut-il envoyer pour s'authentifier ?
Ajoutez Authorization: Bearer YOUR_API_KEY à chaque requête vers https://api.kvkbase.nl. Conservez la clé côté serveur, dans une variable d'environnement, et jamais dans un code exécuté par le navigateur.
Que faire face à une réponse 429 ?
Lisez l'en-tête Retry-After, patientez ce nombre de secondes puis réessayez. Regardez toutefois le code d'erreur : RATE_LIMIT_EXCEEDED se résout en moins d'une minute, alors que QUOTA_EXCEEDED signifie que vos tokens mensuels sont épuisés et qu'attendre ne sert à rien.
Combien de résultats une recherche renvoie-t-elle ?
Au maximum 25 par appel. La réponse contient un champ capped : s'il vaut true, il existe peut-être d'autres résultats et il faut affiner la recherche, par exemple avec un code postal.
Les exemples de ce guide consomment-ils des tokens ?
Une fiche, une recherche ou un contrôle de TVA réussi coûte 1 token. Un numéro d'entreprise invalide est refusé avec INVALID_CBE_NUMBER (400) avant toute recherche, donc sans coût.