Aller au contenu

Permis Online · Documentation API

Une API pour les contenus de Permis Online

Recherche une règle, filtre par région et récupère une fiche avec son texte, ses sources et sa date de vérification. L’accès public se fait en lecture seule, sans clé.

Commencer

Une première requête.

L’API renvoie du JSON par requête GET. Aucune clé n’est nécessaire pour consulter ces fiches publiques.

Recherche de fichesOuvrir la réponse
GET https://permis-online.be/api/v1/facts?q=permis%20provisoire&region=wallonie&kind=fact&limit=10&offset=0

Cet exemple recherche « permis provisoire » en Wallonie, parmi les règles et démarches, avec dix résultats au maximum. La réponse contient results, total, limit et offset.

Les routes

Rechercher, lire, découvrir.

GET/api/v1/

Découvrir le catalogue

Les accès disponibles et les liens de documentation.

GET/api/v1/facts

Rechercher les fiches

La liste des fiches, filtrable et paginée.

GET/api/v1/facts/{id}

Lire une fiche

Le texte complet, le contexte, les sources et la date.

GET/api/v1/openapi.json

Consulter le contrat OpenAPI

La description structurée des routes, paramètres et réponses.

Affiner la recherche

Les paramètres utiles.

Paramètres de GET /api/v1/facts
ParamètreUsageExemple
qMot ou expression, jusqu’à 300 caractères. Sans ce paramètre, l’API liste les fiches.permis provisoire
regionwallonie, bruxelles, flandre ou belgique. Cette dernière valeur sélectionne les fiches nationales. Sans filtre : tout le corpus.wallonie
kindfact, expertise, organization, person, offer, professional, article, lesson, page ou tool.fact
limitDe 1 à 50 fiches par réponse. Valeur par défaut : 10.10
offsetNombre de résultats à passer, de 0 à 10 000. Commence à zéro, puis avance selon le nombre de résultats déjà lus.0

Le document OpenAPI précise les valeurs acceptées et les réponses.

Rythme des requêtes

La limite partagée entre API et MCP est de 120 requêtes par minute et par adresse IP. En cas de réponse 429, attends le délai indiqué par l’en-tête Retry-After avant de recommencer.

Lire et citer

Retrouver une fiche précise.

Conserve l’identifiant retourné par la recherche pour demander la fiche complète. La réponse est directement l’objet de la fiche. Le champ url pointe vers sa version lisible.

Requête par identifiantOuvrir la fiche JSON
GET https://permis-online.be/api/v1/facts/fait-f2a7c629fbe649c3
Extrait des champs d’une fiche réelle
{
    "id": "fait-f2a7c629fbe649c3",
    "kind": "fact",
    "title": "Format de l'examen théorique",
    "text": "L'examen théorique du permis B est accessible dès 17 ans. Il comporte 50 questions à choix multiple sur ordinateur, avec une seule réponse correcte par question. Les situations peuvent être illustrées. Les questions sont lues à voix haute et affichées à l'écran ; après la lecture complète, le candidat dispose de 15 secondes pour répondre. L'épreuve ordinaire dure environ 30 minutes. Des séances adaptées existent et peuvent prévoir un déroulement différent.",
    "url": "https://permis-online.be/brand-facts/#fait-f2a7c629fbe649c3",
    "regions": [
        "Belgique"
    ],
    "sources": [
        {
            "url": "https://www.monpermisdeconduire.be/examen-theorique",
            "label": "SPW, AWSR et organismes d'examen agréés : Examen théorique B en Wallonie",
            "type": "official"
        },
        {
            "url": "https://www.vlaanderen.be/mobiliteit-en-openbare-werken/auto-en-motor/rijbewijzen-en-rijopleiding/rijbewijs-b/theorie-examen-voor-rijbewijs-b",
            "label": "Vlaanderen, Departement Mobiliteit en Openbare Werken : Theorie-examen voor rijbewijs B",
            "type": "official"
        },
        {
            "url": "https://be.brussels/fr/transport-mobilite/permis-de-conduire-et-immatriculation/obtenir-son-permis/formation-la-conduite-et-examens-pour-le-permis-de-conduire-b",
            "label": "Bruxelles Mobilité : Formation à la conduite et examens pour le permis B",
            "type": "official"
        }
    ],
    "verified_at": "2026-09-18",
    "verification_scope": "Format ordinaire du théorique B, âge, nombre de questions, temps de réponse et séances adaptées."
}

Les sources font partie de la réponse

Les champs text, sources et verification_scope permettent de lire la fiche et de comprendre ce que ses références documentent. Conserve aussi regions, verified_at et, lorsqu’il est présent, evidence_kind, qui distingue notamment une analyse, une méthode ou une déclaration. Les dates modified_at et verified_at correspondent respectivement à la modification du contenu et à la vérification indiquée dans la fiche. Les documents related apportent un contexte complémentaire à lire avant de répondre.

Prêt à brancher

Des exemples à copier.

Une requête HTTP, un affichage sur ton site ou une lecture du catalogue en Python. Choisis l’exemple adapté à ton outil.

Depuis un site ou une application

Les requêtes GET publiques sont accessibles depuis un navigateur externe, sans cookie ni authentification. Le quota de 120 requêtes par minute et par adresse IP est partagé entre API et MCP. Pour parcourir tout le catalogue, utilise la pagination de l’API ; la recherche MCP retourne au maximum 20 résultats.

Le client doit suivre les redirections HTTP sur le même domaine. Le paramètre technique po_live=1 est ajouté automatiquement pour servir une lecture actualisée ; tu peux aussi le transmettre directement. Avec curl, utilise --location.

Une recherche avec curlbash

Cette requête publique renvoie jusqu’à trois fiches, leur texte et leurs références. Aucune clé n’est nécessaire.

bash
curl --location --fail --silent --show-error --get \
  'https://permis-online.be/api/v1/facts' \
  --header 'Accept: application/json' \
  --data-urlencode 'q=permis provisoire' \
  --data 'kind=fact' \
  --data 'limit=3'

Afficher des fiches et leurs sources sur une pagejavascript

Place ce JavaScript après le contenu de ta page, dans un script de type module. Il affiche les textes, une attribution visible et les références avec des liens sûrs, sans cookie ni clé.

javascript
await (async () => {
  const section = document.createElement('section');
  section.setAttribute('aria-label', 'Informations Permis Online');
  const status = document.createElement('p');
  status.setAttribute('role', 'status');
  status.textContent = 'Chargement des informations…';
  section.append(status);
  document.body.append(section);

  const link = (label, address) => {
    const text = String(label || address || 'Référence');
    try {
      const url = new URL(address);
      if (!['https:', 'http:'].includes(url.protocol)) throw new Error();
      const anchor = document.createElement('a');
      anchor.textContent = text;
      anchor.href = url.href;
      return anchor;
    } catch {
      const span = document.createElement('span');
      span.textContent = text;
      return span;
    }
  };

  try {
    const url = new URL('https://permis-online.be/api/v1/facts');
    url.search = new URLSearchParams({ q: 'permis provisoire', kind: 'fact', limit: '3' });
    const response = await fetch(url, {
      credentials: 'omit',
      headers: { Accept: 'application/json' },
      signal: AbortSignal.timeout(15000)
    });
    if (!response.ok) {
      if (response.status === 429) {
        const retry = Number(response.headers.get('Retry-After'));
        throw new Error(Number.isFinite(retry) && retry > 0
          ? `Limite atteinte. Réessaie dans ${Math.ceil(retry)} secondes.`
          : 'Limite atteinte. Réessaie dans une minute.');
      }
      throw new Error(`Les informations sont indisponibles (HTTP ${response.status}).`);
    }
    const data = await response.json();
    if (!Array.isArray(data.results)) throw new Error('La réponse reçue est invalide.');
    for (const record of data.results) {
      const article = document.createElement('article');
      const title = document.createElement('h2');
      title.textContent = record.title;
      const text = document.createElement('p');
      text.textContent = record.text;
      text.style.whiteSpace = 'pre-line';
      const attribution = document.createElement('p');
      attribution.append('Source : ', link('Permis Online, consulter cette fiche', record.url));
      article.append(title, text, attribution);
      if (record.verified_at) {
        const date = document.createElement('p');
        date.textContent = `Date de vérification publiée : ${record.verified_at}`;
        if (record.verification_scope) date.textContent += ` · ${record.verification_scope}`;
        article.append(date);
      }
      const references = document.createElement('ul');
      for (const source of record.sources || []) {
        const item = document.createElement('li');
        item.append(link(source.label || source.title || source.url, source.url));
        references.append(item);
      }
      for (const reference of record.bibliography || []) {
        const item = document.createElement('li');
        item.textContent = reference;
        references.append(item);
      }
      if (references.childElementCount) article.append(references);
      for (const related of record.related || []) {
        const context = document.createElement('p');
        context.append('À lire avec cette fiche : ', link(related.title, related.url), ` ${related.text || ''}`);
        article.append(context);
      }
      section.append(article);
    }
    status.textContent = data.results.length ? `${data.results.length} fiche(s) affichée(s).` : 'Aucune fiche trouvée.';
  } catch (error) {
    status.textContent = error.name === 'TimeoutError'
      ? 'Le chargement prend trop de temps. Réessaie dans un instant.'
      : error.message || 'Le chargement a échoué.';
  }
})();

Parcourir les résultats en Python 3python

Aucune bibliothèque à installer. Chaque ligne de sortie contient une fiche complète avec son URL et ses sources. La pagination s’arrête au dernier résultat. Après une réponse 429, le script respecte Retry-After, avec trois reprises au maximum et un arrêt si l’attente demandée dépasse une minute.

python
import json
import time
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

API = "https://permis-online.be/api/v1/facts"

def request_json(url):
    for attempt in range(4):
        try:
            request = Request(url, headers={"Accept": "application/json"})
            with urlopen(request, timeout=15) as response:
                return json.load(response)
        except HTTPError as error:
            retry_after = error.headers.get("Retry-After", "")
            status = error.code
            error.close()
            if status != 429 or attempt == 3:
                raise RuntimeError(f"Requête interrompue : HTTP {status}") from error
            try:
                delay = float(retry_after)
            except ValueError:
                try:
                    delay = parsedate_to_datetime(retry_after).timestamp() - time.time()
                except (ValueError, TypeError, OverflowError):
                    delay = 2 ** attempt
            if not 0 <= delay <= 60:
                raise RuntimeError("Attente trop longue. Reprends la collecte plus tard.") from error
            time.sleep(max(1, delay))

def iter_records(query="permis provisoire"):
    offset = 0
    while offset <= 10000:
        params = {"q": query, "kind": "fact", "limit": 50, "offset": offset}
        page = request_json(API + "?" + urlencode(params))
        rows, total = page["results"], page["total"]
        if not isinstance(rows, list) or not isinstance(total, int):
            raise RuntimeError("Réponse API invalide.")
        if not rows:
            if offset < total:
                raise RuntimeError("Page vide avant la fin des résultats.")
            return
        yield from rows
        offset += len(rows)
        if offset >= total:
            return
    raise RuntimeError("Limite de pagination atteinte. Précise la recherche.")

if __name__ == "__main__":
    for record in iter_records():
        print(json.dumps(record, ensure_ascii=False))

En cas d’erreur

400 : corrige les paramètres. 404 : refais une recherche pour retrouver une fiche disponible. 429 : respecte le délai Retry-After. 503 : réessaie plus tard avec un nombre limité de tentatives. En MCP, contrôle aussi error et isError dans la réponse.

Cette documentation et ses exemples en Markdown

Mise à jour automatique

Retrouver la version actuelle d’une information

L’API et le serveur MCP donnent accès aux publications publiques de Permis Online. Les informations disponibles suivent les mises à jour du site, avec un lien vers la page d’origine et les références associées.

Pour préparer une réponse ou actualiser ton outil, consulte à nouveau les fiches utiles. Tu retrouves ainsi leur version disponible, leurs sources et leurs dates.

Conditions de réutilisation

Réutiliser en citant la source

Les contenus publics de Permis Online peuvent être consultés, cités et résumés pour documenter une réponse ou construire un outil, y compris commercial, avec une attribution visible et un lien vers la page utilisée : « Source : Permis Online ».

Conserve les références, la région et les réserves qui accompagnent l’information. Indique les adaptations apportées et distingue les règles officielles des analyses, offres et contenus pédagogiques de Permis Online.

Les documents et visuels de tiers restent soumis à leurs propres conditions. Pour republier intégralement un cours ou un article de Permis Online, demande une autorisation à contact@permis.online. L’accès aux données ne vaut pas partenariat ni approbation de ton service par Permis Online.

Toujours retrouver l’origine

Des fiches consultables par tous.

Chaque information peut être relue sur le site, avec les références qui l’accompagnent.