# API des contenus publics de Permis Online

Documentation de l’API publique Permis Online : recherche, régions, sources, dates de vérification et exemples JSON.

Page : https://permis.online/donnees/api/

Corpus : 355 fiches publiques, avec leurs liens, sources, régions et dates lorsqu’elles sont documentées. Chaque fiche précise son contexte et les références qui documentent l’information.

## Utilisation

Accès public, lecture seule, sans clé. Ne contient ni compte, ni progression d’élève, ni information privée. Citer le lien de la fiche et conserver ses sources et la portée de la vérification. Les analyses et méthodes de Permis Online sont identifiées séparément des règles officielles.

## API REST

- Catalogue : https://permis.online/api/v1/
- Recherche : https://permis.online/api/v1/facts?q=permis&limit=10
- OpenAPI : https://permis.online/api/v1/openapi.json

GET /api/v1/facts accepte q (300 caractères maximum), region (wallonie, bruxelles, flandre, belgique), kind (fact, expertise, organization, offer, person, professional, article, lesson, page, tool), limit (1 à 50, défaut 10), offset (0 à 10000, défaut 0). Les mots significatifs doivent tous figurer dans le titre, le contenu, les références ou le contexte lié. Le filtre Belgique sélectionne les fiches nationales. Réponse : {results: [fiches], total, limit, offset}.

GET /api/v1/facts/{id} renvoie la fiche directement. Conserver l’identifiant exact renvoyé par la recherche. Les préfixes fait-, expertise-fait-, brand-, person-, offer-, professional- et page- sont suivis de 16 caractères hexadécimaux.

Codes : 200 résultat, 400 paramètres invalides, 404 fiche inconnue, 405 méthode refusée, 429 limite atteinte, 503 service indisponible.

## Accès navigateur

CORS public pour GET, sans cookie ni clé. Utiliser credentials: 'omit'. Retry-After est lisible depuis les sites externes. Suivre les redirections HTTP sur le même domaine (curl : --location). Le paramètre technique po_live=1 est ajouté automatiquement à la redirection pour une lecture actualisée et peut aussi être envoyé directement. Pour lire tout le catalogue, parcourir l’API paginée et dédupliquer par id. Un ajout ou un retrait pendant le parcours peut déplacer les résultats : il ne s’agit pas d’un instantané figé.

## Exemples exécutables

### Une recherche avec curl

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/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 page

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/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 3

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

## 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.

## 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.

## Limites et références

120 requêtes par minute et par adresse IP, partagées entre API et MCP. En cas de réponse 429, respecter Retry-After ; 400 : corriger les paramètres ; 404 : rechercher à nouveau ; 503 : réessayer plus tard avec des tentatives bornées. Le service restitue les fiches publiées. Le champ bibliography contient les références bibliographiques citées dans les cours.

- [Fiches de référence](https://permis.online/brand-facts/)
- [Sources et références](https://permis.online/sources/)
- [Fiches en Markdown](https://permis.online/brand-facts.md)
- [Fiches en JSON](https://permis.online/.well-known/brand-facts.json)
- [Index pour les assistants](https://permis.online/llms.txt)
- [Données et intégrations](https://permis.online/donnees/)
- [API des contenus publics de Permis Online](https://permis.online/donnees/api/)
- [Serveur MCP Permis Online](https://permis.online/donnees/mcp/)
