API-stabiliteitscontract — /v1/norms en /v2/norms¶
Dit document is het bindende stabiliteitscontract voor afnemers van /v1/norms en /v2/norms. Het definieert waar afnemers op kunnen vertrouwen, hoe wijzigingen efficiënt te detecteren zijn, en welke soorten wijzigingen een major versie-update rechtvaardigen.
/v1/norms is uitgefaseerd — gebruik /v2/norms
/v1/norms is uitgefaseerd per 1 november 2026 en verdwijnt op
1 november 2028 (24 maanden, volgens het uitfaseringsbeleid).
Elk v1-antwoord bevat Deprecation: @1793491200,
Sunset: Wed, 01 Nov 2028 00:00:00 GMT en
Link: </v2/norms>; rel="successor-version". Tot de sunset gedraagt v1
zich precies zoals hieronder beschreven.
/v2/norms¶
/v2/norms beantwoordt de vraag die afnemers werkelijk stellen: welke normen
gelden op deze datum? De volledige beschrijving staat op
API Specification (v2), uit /v2/openapi.json.
/v1/norms |
/v2/norms |
|
|---|---|---|
Default cprmv_version |
0.3.0 |
0.4.1 |
| Datumparameter | applicable_date: exacte match op de begindatum van een periode |
valid_on: de normen die op die datum gelden |
| Zonder datum | alle perioden van alle regelsets | geldend vandaag (Europe/Amsterdam) |
dataset_versions |
alle records van elke regelset in het antwoord | de records van de geldende periode |
| Envelop | — | voegt valid_on toe, de datum waarvoor het antwoord geldt |
Selectie. Per regelset geeft /v2/norms de regels van de laatste periode
waarvan de begindatum (applicable_date) op of vóór valid_on ligt. Een
periode blijft gelden tot dezelfde regelset een latere publiceert.
Regelsets waarvan alle perioden na valid_on beginnen, ontbreken. Een
antwoord mengt dus perioden: op 2026-08-15 bevat het BWBR0002471 vanaf
2026-02-21, BWBR0015711 vanaf 2026-01-01 en de regelsets waarvan de periode
op 2026-07-01 begint.
Parameters. valid_on moet een bestaande kalenderdatum zijn
(2026-02-30 wordt geweigerd). applicable_date wordt geweigerd met een 400
die naar valid_on verwijst. rulesetid, cprmv_version en endpoint
werken zoals op v1; op beide versies wordt een rulesetid of cprmv_version
die als array binnenkomt (?rulesetid[]=X) beantwoord met 400 INVALID_PARAM.
Datasetversies. Voor 0.3.x-regelsets waarvan de records geen versie
dragen, bevat dataset_versions de records zonder versie, en is [0] de
meest recent gepubliceerde daarvan, niet noodzakelijk het record van de
geldende periode.
Caching. De ETag is strong en dekt de datasetmetadata van de gekozen
perioden, de requestparameters (endpoint, rulesetid, cprmv_version en de
gebruikte valid_on, plus een markering die v2-ETags van v1-ETags
onderscheidt) en een digest van de inhoud van de gekozen regels. Een correctie binnen een periode (een hogere
rulesetid_index) verandert dus de ETag, en middernacht ook, zelfs als er
niets opnieuw is gepubliceerd, want er kan een nieuwe periode zijn ingegaan.
Last-Modified ontbreekt wanneer die na het moment van het antwoord zou liggen
(een periode die nog niet is begonnen); revalideer daarom met If-None-Match; wordt die meegestuurd, dan wordt
If-Modified-Since genegeerd (RFC 9110 §13.2.2). Op v2 is er geen
304 vóór de query voor rulesetid-requests: de 304-check draait na de
rules-query, omdat de digest de regels nodig heeft. Zonder valid_on reikt
max-age nooit voorbij de eerstvolgende middernacht in Europe/Amsterdam.
Stabiliteit. Binnen v2 veranderen de vorm van het default-antwoord
(0.4.1), de predicaat-URI's en de betekenis van valid_on niet; de
defaultversie van v2 verandert pas met een /v3/norms. 0.3.0 en 0.3.2
blijven op v2 selecteerbaar, zonder die garantie. De primaire sleutel
(rulesetid, applicable_date, rulesetid_index) en de regels voor additieve
evolutie hieronder gelden voor v2 net als voor v1. De OpenAPI-beschrijving
staat op /v2/openapi.json.
Doelgroep¶
Dit contract richt zich op G2G-afnemers — andere Nederlandse overheidsdiensten die /v1/norms of /v2/norms integreren om cprmv:Rule-paden en normen af te nemen. Externe afnemers kunnen op basis van dit contract langetermijnintegraties bouwen zonder vrees voor breuk binnen een major versie.
De vier versielagen¶
Elke /vN/norms draagt vier verschillende versienummers, elk voor een andere stabiliteitslaag:
| Laag | Locatie | Wat het bijhoudt | Wanneer het verandert |
|---|---|---|---|
| API-contract | URL-pad /v1/, /v2/ |
Het schema en de vorm waar afnemers tegen aan coderen | Uitsluitend breaking changes (rechtvaardigt een nieuw major pad) |
| Dataset-versies | data.dataset_versions-envelopmap |
Publicatie-snapshots per rulesetid | Elke BWB-regelset met eigen cadans; de map bevat de meest recente versie per rulesetid in het antwoord |
| CPRMV-vocabulaire | ?cprmv_version=-requestparameter ↔ data.cprmv_version-envelopveld |
In welke CPRMV-vocabulaireversie de respons wordt gevraagd en uitgeleverd | Door de afnemer gekozen per request (0.3.0, 0.3.2, 0.4.1; default 0.3.0 op v1, 0.4.1 op v2); de namespace van de data volgt de keuze |
| Backend-service | API-Version HTTP-header |
De uitgerolde backendcode | Bij elke backendrelease (operationeel, geen contractsignaal) |
Alleen de eerste drie maken deel uit van het afnemerscontract. De API-Version-header is informatief — bruikbaar voor supporttickets, niet voor cache-invalidatie of schema-onderscheid.
cprmv_version is nu een request-input
Sinds backend v1.9.10 is cprmv_version een onderhandelbare laag: afnemers kiezen de
vocabulaireversie via de optionele parameter ?cprmv_version= (default 0.3.0), en het
envelopveld cprmv_version echoot de keuze. Voorheen gaf het alleen "wat de backend spreekt"
weer. De parameter weglaten behoudt exact het eerdere gedrag, dus dit is een additieve
wijziging binnen v1.
Stabiliteitsbelofte binnen v1¶
Reikwijdte
Deze sectie en de volgende beschrijven het uitgefaseerde /v1/norms. /v2/norms wordt gedekt door de sectie /v2/norms hierboven.
Primaire-sleutelsemantiek¶
De tupel (rulesetid, applicable_date, rulesetid_index) is de onveranderlijke primaire sleutel voor elke individuele regel. Zodra gepubliceerd, identificeert deze combinatie een regel waarvan de waarden nooit meer veranderen.
- Correcties worden gepubliceerd als nieuwe rijen met een hogere
rulesetid_index - Wetswijzigingen worden gepubliceerd als nieuwe rijen met een nieuwe
applicable_date - Oude rijen blijven ongewijzigd en oneindig opvraagbaar
Afnemers kunnen rijen onbeperkt cachen op basis van deze tupel. Cache-invalidatie is niet vereist.
Logische identiteit over versies¶
Het veld rule_id_path_key biedt een stabiele identificator voor de logische regel over al zijn versies heen. Om "de huidige waarde van deze regel" op te vragen:
- Filter regels op
rule_id_path_key - Kies de rij met de meest recente
applicable_date - Kies daarbinnen de rij met de hoogste
rulesetid_index
De aard van de wijziging is af te leiden uit de inhoud — een nieuwe applicable_date impliceert een wetswijziging; dezelfde datum met een hogere rulesetid_index impliceert een correctie. Er wordt geen aparte metadata over het soort wijziging gepubliceerd.
Additieve evolutie¶
Binnen v1 zijn alle wijzigingen additief:
- Nieuwe velden kunnen verschijnen in de responsenvelop of in regelobjecten — afnemers moeten onbekende velden netjes negeren
- Nieuwe optionele queryparameters kunnen worden toegevoegd — afnemers mogen deze negeren
- Bestaande velden, hun namen, types en semantiek veranderen niet
CPRMV-vocabulaireversie¶
Het envelopveld cprmv_version echoot de versie die via ?cprmv_version= is gevraagd (een van 0.3.0, 0.3.2, 0.4.1; default 0.3.0). De volledig-gekwalificeerde regelsleutels (type, id, definition, contains) dragen de namespace van die versie, dus de predicate-URI's die een afnemer ziet zijn een functie van de versie die hij opvraagt:
0.3.0/0.3.2→https://cprmv.open-regels.nl/<versie>/…0.4.1→https://standaarden.open-regels.nl/standards/cprmv/0.4.1#…
Stabiliteit binnen v1: de default-respons (0.3.0) en zijn predicate-URI's veranderen niet. Een andere cprmv_version opvragen is een expliciete opt-in voor die namespace — geen breaking change aan het default-contract. Een afnemer die de parameter nooit meestuurt, wordt niet geraakt door nieuwe versies in de ondersteunde set. Een wijziging van de default-versie, of van de predicate-URI's van de default-versie, vraagt een nieuw major pad: dat is /v2/norms, met 0.4.1 als default.
Experimentele versies — 0.3.2 en 0.4.1 vallen niet onder de v1-garantie
Op /v1/norms valt alleen de default 0.3.0 onder dit stabiliteitscontract.
0.3.2 en 0.4.1 zijn op v1 experimenteel / preview: hun responsvorm,
predicate-URI's, dataset_versions-semantiek (bijv. het 0.4.1-cprmv:RuleSet/validFrom-model
en de cachekanttekening) en zelfs hun beschikbaarheid op v1
kunnen wijzigen — of worden ingetrokken — zonder een nieuw major pad en buiten de
additieve-evolutiebelofte hierboven. Dit geldt alleen voor v1: op /v2/norms is 0.4.1
de contractdefault (zie /v2/norms). Bouw langetermijn-G2G-integraties
tegen /v2/norms.
Datasetversiebeheer per rulesetid¶
Elke BWB-regelset (BWBR0002471, BWBR0004044, …) draagt versiemetadata per regelset, gepubliceerd door de CPSV editor. Waar die metadata staat hangt af van de gevraagde cprmv_version: 0.3.0/0.3.2 gebruiken een afzonderlijke cprmv:Dataset-resource per regelset; 0.4.1 heeft geen cprmv:Dataset en de metadata wordt uit de cprmv:RuleSet gelezen (cprmv:validFrom). Hoe dan ook is de envelopvorm hieronder identiek. Eén regelset kan meerdere records hebben — verschillende toepasselijke perioden van dezelfde wet (bijv. de edities 2025-01-01 en 2026-01-01 van de Participatiewet) zijn gelijktijdig en even gezaghebbend, geen concurrerende versies van elkaar. Beide ondersteunen regels die afnemers legitiem kunnen opvragen. Eén /v1/norms-respons kan regels over N regelsets aggregeren, elk met M records.
De dataset_versions-map¶
Het envelopveld data.dataset_versions is gekeyd op cprmv:rulesetId; elke waarde is een lijst van records:
"dataset_versions": {
"BWBR0015703": [
{
"version": "2026-01-01",
"published_at": "2026-05-15T06:57:21Z",
"title": "Participatiewet"
},
{
"version": "2025-01-01",
"published_at": "2026-05-15T07:45:36Z",
"title": "Participatiewet"
}
],
"BWBR0044894": [
{ "version": null, "published_at": "2026-05-15T07:45:36Z", "title": null }
]
}
De lijst is vooraf gesorteerd: version aflopend met nulls achteraan, gelijke waarden gebroken door published_at aflopend. Element [0] is de meest recente toepasselijke versie van die regelset.
De map bevat alleen entries voor rulesetid's die ten minste één versierecord hebben (een cprmv:Dataset voor 0.3.x, een cprmv:RuleSet voor 0.4.1). Rulesetid's zonder zo'n record zijn stilzwijgend afwezig (een overgangsstaat voor legacy 0.3.x-data; 0.4.1 heeft altijd een RuleSet per regelset).
Drie velden per entry:
| Veld | Bron (0.3.x / 0.4.1) | Opmerkingen |
|---|---|---|
version |
dcat:version / cprmv:validFrom |
Aanwezig voor elke regelset sinds CPSV editor v1.10.5 — de editor leidt de versie van elke regelset af uit de BWB-datum die de eigen regels dragen (hun ruleIdPath), zodat ook niet-primaire regelsets zijn geversioneerd. null blijft alleen over voor legacy-data van vóór die wijziging; afnemers moeten dit nog steeds tolereren. |
published_at |
dct:issued / cprmv:validFrom |
0.3.x: het publicatietijdstempel — wordt bij elke (her)publicatie bijgewerkt, het signaal voor wijzigingsdetectie. 0.4.1: er is geen dct:issued, dus cprmv:validFrom dient als published_at (zie de 0.4.1-cachekanttekening). |
title |
dct:title |
Alleen primaire regelset. De editor kent alleen de menselijke titel van het legalResource.bwbId van de dienst. null voor niet-primaire regelsets. |
Regels koppelen aan Dataset-records¶
Een regel met applicable_date: "2025-07-01" wordt ondersteund door het Dataset-record waarvan de version die periode dekt. De koppeling is conventiegebaseerd, niet afgedwongen door de API: de editor publiceert Dataset-records voor dezelfde toepasselijke perioden als de regels die hij genereert. Afnemers die "het Dataset-record dat deze regel ondersteunt" willen vinden, kunnen:
- Opzoeken
dataset_versions[<rule.rulesetid>] - De entry vinden waarvan de
versionovereenkomt met<rule.applicable_date>(wanneer version bekend is) - Doorvallen naar de meest recente entry op
published_atwanneer version null is
Publicaties detecteren¶
HTTP-cacheheaders¶
Wanneer elke rulesetid in de respons ten minste één dataset_versions-entry heeft, draagt de respons:
| Header | Voorbeeld | Betekenis |
|---|---|---|
ETag |
"3c899856" |
Opaque strong validator over elk (version, published_at)-paar plus filterparameters. title is bewust uitgesloten — uitsluitend informatief. |
Last-Modified |
Fri, 15 May 2026 07:45:36 GMT |
Maximum published_at over alle records in de respons |
Cache-Control |
public, max-age=3600 |
Halfjaarlijkse data verdraagt ruime caching |
Afnemers wordt aangeraden conditionele requests te gebruiken voor efficiëntie:
De server retourneert 304 Not Modified zonder body wanneer er sinds de laatste fetch niets in de respons opnieuw is gepubliceerd. Voor queries op een enkele rulesetid (?rulesetid=<id>) vindt de 304-check plaats vóór de dure rules-SPARQL-query — alleen de goedkope (gecachete) metadata-lookup draait.
Waarom published_at (niet version) de cachegeldigheid bepaalt¶
ETag en Last-Modified worden berekend uit published_at, niet uit version; het veld version is informatieve metadata voor menselijke en UI-consumptie. Voor 0.3.0/0.3.2 is published_at gelijk aan dct:issued, dat bij elke publicatiegebeurtenis wordt bijgewerkt en daarmee een betrouwbaar wijzigingssignaal is (een legacy-null voor version maakt de data niet on-cachebaar).
0.4.1-cachekanttekening
Op /v1/norms met cprmv_version=0.4.1 is published_at gelijk aan cprmv:validFrom (de toepasselijke
datum), omdat de 0.4.1-RuleSet geen dct:issued heeft. Een herpublicatie die dezelfde
validFrom behoudt maar regelwaarden corrigeert, verandert de ETag/Last-Modified
niet, dus een gecachete respons blijft op /v1/norms als ongewijzigd gevalideerd: de ETag
verandert pas als een latere periode wordt gepubliceerd. Afnemers die op 0.4.1
correctie-actuele data nodig hebben, gebruiken /v2/norms, waarvan de ETag ook
de inhoud van de gekozen regels dekt, zodat een correctie binnen een periode hem wijzigt.
0.3.x wordt niet geraakt (dct:issued loopt op bij elke publicatie).
Gedrag bij gedeeltelijke dekking¶
Wanneer een rulesetid in de respons een versierecord mist (een cprmv:Dataset voor 0.3.x, een cprmv:RuleSet voor 0.4.1), degradeert de respons als volgt:
- De
dataset_versions-map laat de niet-versie-gebonden rulesetid(s) weg - De headers
ETagenLast-Modifiedworden niet gezet Cache-Control: no-cache
Rationale: we kunnen een wijziging in een niet-versie-gebonden regelset niet betrouwbaar detecteren. Een 304 retourneren in dat geval zou het risico van verouderde data opleveren, dus vertellen we afnemers altijd opnieuw op te halen. Naarmate meer BWB-regelsets worden gepubliceerd met versiemetadata, treedt caching geleidelijk in werking voor queries die uitsluitend versie-gebonden regelsets bestrijken.
Wat rechtvaardigt /v3/norms¶
Het volgende zou het v2-contract doorbreken en zou worden uitgebracht als /v3/norms, terwijl /v2/norms levend wordt gehouden gedurende een uitfaseringsperiode. Dezelfde regels zijn de reden dat de v1-default (0.3.0) niet ter plekke kon veranderen en /v2/norms is geïntroduceerd:
- Een bestaand veld verwijderen of hernoemen
- Het type of de semantiek van een bestaand veld wijzigen
- De PK-semantiek van
(rulesetid, applicable_date, rulesetid_index)wijzigen (bijv. in-place mutatie toestaan) - CPRMV major versie-update die de predicate-URI's verandert die de afnemer ziet
Uitfaseringsbeleid¶
Met de introductie van /v2/norms:
/v1/normsblijft beschikbaar gedurende ten minste 24 maanden na publicatie van/v2/norms- Tijdens de uitfasering bevatten
/v1/norms-responsen de headersDeprecation: @1793491200(1 november 2026) enSunset: Wed, 01 Nov 2028 00:00:00 GMT(Deprecationconform RFC 9745,Sunsetconform RFC 8594), plus eenLink: </v2/norms>; rel="successor-version"-header - Actieve afnemers worden geïnformeerd via de documentatiesite van het IOU Architectuur en de changelog
Snelle referentie voor afnemers¶
| Vraag | Antwoord |
|---|---|
| Mag ik de waarden van een regel onbeperkt cachen? | Ja, gekeyd op (rulesetid, applicable_date, rulesetid_index) |
| Hoe detecteer ik nieuwe publicaties efficiënt? | Gebruik If-None-Match met de vorige ETag — 304 betekent dat er niets is gewijzigd (op /v1/norms met cprmv_version=0.4.1 wordt een correctie binnen een periode niet gedetecteerd; gebruik /v2/norms) |
Welke cprmv_version moet ik opvragen? |
Op /v2/norms: laat hem weg voor de stabiele default (0.4.1). Op het uitgefaseerde /v1/norms is de default 0.3.0, waarin de editor geen nieuwe perioden meer publiceert. |
| Hoe krijg ik de normen die op een datum gelden? | GET /v2/norms?valid_on=JJJJ-MM-DD; laat valid_on weg voor vandaag. |
Wat als een rulesetid ontbreekt in dataset_versions? |
Die regelset heeft nog geen versierecord (een cprmv:Dataset voor 0.3.x / een cprmv:RuleSet voor 0.4.1); niet cachen |
Wat betekent Cache-Control: no-cache hier? |
Ten minste één rulesetid in uw query is niet versie-gebonden — telkens opnieuw ophalen |
Wat betekent version: null? |
Legacy-data gepubliceerd vóór CPSV editor v1.10.5 — de niet-primaire versie was onbekend. Actuele data versioneert elke regelset, dus null is zeldzaam. published_at blijft gezaghebbend voor wijzigingsdetectie (met de 0.4.1-kanttekening). |
Is published_at altijd een publicatietijdstempel? |
Voor 0.3.x wel (dct:issued). Voor 0.4.1 is het cprmv:validFrom (de toepasselijke datum) — een herpublicatie op dezelfde datum verhoogt het niet; zie de 0.4.1-cachekanttekening. |
| Waarom heeft één rulesetid meerdere Dataset-records? | Verschillende toepasselijke perioden van dezelfde wet zijn gelijktijdig en even gezaghebbend. De edities 2025-01-01 en 2026-01-01 van de Participatiewet ondersteunen beide actuele regels; beide worden vermeld. |
| Hoe vind ik welk Dataset-record een specifieke regel ondersteunt? | Zoek op dataset_versions[<rule.rulesetid>], vind de entry waarvan version overeenkomt met <rule.applicable_date>; val door naar de meest recente op published_at wanneer version null is. |
| Hoe vind ik de huidige waarde van een regel? | Filter op rule_id_path_key, sorteer op applicable_date desc en vervolgens rulesetid_index desc, neem de eerste |
| Verschijnen er nieuwe velden in responsen? | Ja — additief, nooit als breaking change. Negeer onbekende velden |
| Staan alle BWB-regelsets op dezelfde publicatiecyclus? | Nee — elke regelset heeft een eigen cadans; controleer dataset_versions[<id>][0].published_at per regelset |