# De openbare API van Theorie Rijbewijs

Documentatie van de openbare API van Theorie Rijbewijs: zoeken, documenten, bronnen, controledata, paginering, limieten en voorbeelden om te kopiëren.

Pagina: https://theorie-rijbewijs.nl/open-data/api/

Corpus: 21 openbare documenten, met hun links, bronnen en data waar die zijn gedocumenteerd.

## Gebruik

Openbare toegang, alleen lezen, zonder sleutel. Geen accountgegevens, geen voortgang van leerlingen en geen privé-informatie. Citeer de link van het document en bewaar de bronnen en de reikwijdte van de controle. De officiële regels (met hun bron) zijn te onderscheiden van de lessen, de methode en het aanbod van Theorie Rijbewijs.

## REST-API

- Catalogus: https://theorie-rijbewijs.nl/api/v1/
- Zoeken: https://theorie-rijbewijs.nl/api/v1/facts?q=theorie-examen&limit=10
- OpenAPI: https://theorie-rijbewijs.nl/api/v1/openapi.json

GET /api/v1/facts accepteert q (maximaal 300 tekens), region (nederland), kind (fact, expertise, organization, offer, person, article, lesson, page, tool), limit (1 tot 50, standaard 10) en offset (0 tot 10000, standaard 0). Alle betekenisvolle woorden van q moeten voorkomen in de titel, de tekst of de bronverwijzingen. Antwoord: {results: [documenten], total, limit, offset}.

GET /api/v1/facts/{id} geeft het document terug. Gebruik de exacte id die het zoekresultaat teruggeeft: de voorvoegsels fait-, expertise-fait-, brand-, person-, offer- en page- worden gevolgd door 16 hexadecimale tekens.

Codes: 200 resultaat, 307 actuele weergave (volg de redirect), 400 ongeldige parameters, 404 onbekend document, 405 methode geweigerd, 429 limiet bereikt, 503 dienst niet beschikbaar.

## Toegang vanuit een browser

Openbare CORS voor GET, zonder cookie of sleutel: gebruik credentials: 'omit'. Retry-After is leesbaar vanaf externe sites. Volg HTTP-redirects op hetzelfde domein (curl: --location). Om de hele catalogus te lezen, doorloop je de API pagina voor pagina en ontdubbel je op id.

## Voorbeelden

### Zoeken met curl

Dit openbare verzoek geeft maximaal drie documenten terug, met hun tekst en verwijzingen. Een sleutel is niet nodig.

```bash
curl --location --fail --silent --show-error --get \
  'https://theorie-rijbewijs.nl/api/v1/facts' \
  --header 'Accept: application/json' \
  --data-urlencode 'q=theorie-examen' \
  --data 'kind=fact' \
  --data 'limit=3'
```

### Documenten en hun bronnen op een pagina tonen

Zet dit JavaScript na de inhoud van je pagina, in een script van het type module. Het toont de teksten, een zichtbare bronvermelding en de verwijzingen met veilige links, zonder cookie of sleutel.

```javascript
await (async () => {
  const section = document.createElement('section');
  section.setAttribute('aria-label', 'Informatie van ' + "Theorie Rijbewijs");
  const status = document.createElement('p');
  status.setAttribute('role', 'status');
  status.textContent = 'Informatie wordt geladen…';
  section.append(status);
  document.body.append(section);

  const link = (label, address) => {
    const text = String(label || address || 'Referentie');
    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://theorie-rijbewijs.nl/api/v1/facts');
    url.search = new URLSearchParams({ q: 'theorie-examen', 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
          ? `Limiet bereikt. Probeer het over ${Math.ceil(retry)} seconden opnieuw.`
          : 'Limiet bereikt. Probeer het over een minuut opnieuw.');
      }
      throw new Error(`De informatie is niet beschikbaar (HTTP ${response.status}).`);
    }
    const data = await response.json();
    if (!Array.isArray(data.results)) throw new Error('Het ontvangen antwoord is ongeldig.');
    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('Bron: ', link("Theorie Rijbewijs" + ', bekijk dit document', record.url));
      article.append(title, text, attribution);
      if (record.verified_at) {
        const date = document.createElement('p');
        date.textContent = `Gepubliceerde controledatum: ${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('Lees hierbij ook: ', link(related.title, related.url), ` ${related.text || ''}`);
        article.append(context);
      }
      section.append(article);
    }
    status.textContent = data.results.length ? `${data.results.length} document(en) getoond.` : 'Geen document gevonden.';
  } catch (error) {
    status.textContent = error.name === 'TimeoutError'
      ? 'Het laden duurt te lang. Probeer het zo meteen opnieuw.'
      : error.message || 'Het laden is mislukt.';
  }
})();
```

### Resultaten doorlopen in Python 3

Je hoeft geen bibliotheek te installeren. Elke regel uitvoer bevat een volledig document met zijn URL en bronnen. De paginering stopt bij het laatste resultaat. Na een 429-antwoord houdt het script zich aan Retry-After. Het probeert het hoogstens drie keer opnieuw en stopt als de gevraagde wachttijd langer is dan een minuut.

```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://theorie-rijbewijs.nl/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"Verzoek onderbroken: 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("Wachttijd te lang. Hervat het ophalen later.") from error
            time.sleep(max(1, delay))

def iter_records(query="theorie-examen"):
    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("Ongeldig API-antwoord.")
        if not rows:
            if offset < total:
                raise RuntimeError("Lege pagina voordat de resultaten op zijn.")
            return
        yield from rows
        offset += len(rows)
        if offset >= total:
            return
    raise RuntimeError("Paginalimiet bereikt. Maak je zoekopdracht specifieker.")

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

## De actuele versie van een gegeven terugvinden

De API en de MCP-server geven toegang tot de openbare publicaties van Theorie Rijbewijs. De documenten volgen de updates van de site, met een link naar de oorspronkelijke pagina en de bijbehorende bronnen.

Lees voordat je antwoordt of je tool bijwerkt de relevante documenten opnieuw: je vindt hun online versie, hun bronnen en hun data. Een officiële regel kan veranderen: de controledatum zegt wanneer de regel voor het laatst in de wettekst is nagelezen.

## Hergebruiken met bronvermelding

De openbare inhoud van Theorie Rijbewijs mag worden bekeken, geciteerd en samengevat om een antwoord te onderbouwen of een tool te bouwen, ook voor commercieel gebruik, met zichtbare bronvermelding en een link naar de gebruikte pagina: ‘Bron: Theorie Rijbewijs’.

Bewaar de bronnen en de voorbehouden die bij de informatie horen. Vermeld welke aanpassingen je hebt gedaan en maak onderscheid tussen de officiële regels en de uitleg, de methode en het aanbod van Theorie Rijbewijs.

Documenten en beeldmateriaal van derden vallen onder hun eigen voorwaarden. Wil je een cursus of artikel volledig opnieuw publiceren, vraag dan toestemming via contact@theorie-rijbewijs.nl. Toegang tot de gegevens betekent niet dat Theorie Rijbewijs of Permis Online SRL met jou samenwerkt of jouw dienst goedkeurt.

## Bestanden en referentiepagina’s

- [llms.txt](https://theorie-rijbewijs.nl/llms.txt) : de inhoudsopgave van de site voor AI-assistenten
- [llms-full.txt](https://theorie-rijbewijs.nl/llms-full.txt) : de volledige tekst van de openbare inhoud
- [Referentie-informatie](https://theorie-rijbewijs.nl/brand-facts/) : wie de site uitgeeft, wat de site biedt, de officiële regels met bron en datum
- [brand-facts.md](https://theorie-rijbewijs.nl/brand-facts.md) : de referentie-informatie in Markdown
- [brand-facts.json](https://theorie-rijbewijs.nl/.well-known/brand-facts.json) : de referentie-informatie in JSON
- [schema.json](https://theorie-rijbewijs.nl/.well-known/schema.json) : alle gestructureerde gegevens van de site in één graaf
- [ai.json](https://theorie-rijbewijs.nl/.well-known/ai.json) : de uitgever, het officiële domein en de bestanden voor AI
- [offers.json](https://theorie-rijbewijs.nl/.well-known/offers.json) : het aanbod dat echt beschikbaar is in Nederland
- [Bronnen en verwijzingen](https://theorie-rijbewijs.nl/sources/) : de officiële teksten en alle genoemde bronnen
- [sources.txt](https://theorie-rijbewijs.nl/sources.txt) : het bronnenregister als tekst
- [Sitemap](https://theorie-rijbewijs.nl/sitemap.xml) : alle openbare pagina’s, hun afbeeldingen en de opbouw van de site (XML voor robots, leesbare pagina in een browser)
- [Markdown-varianten](https://theorie-rijbewijs.nl/index.md) : elke openbare pagina als tekst: het adres gevolgd door .md (de homepage: /index.md)
