API strategie voor de Nederlandse overheid - Extensies

Geonovum Handreiking
Versie ter vaststelling

Deze versie:
https://docs.geostandaarden.nl/api/vv-hr-API-Strategie-ext-20190715/
Laatst gepubliceerde versie:
https://docs.geostandaarden.nl/api/API-Strategie-ext/
Laatste werkversie:
https://geonovum.github.io/KP-APIs/
Redacteurs:
Jasper Roes, Het Kadaster
Linda van den Brink, Geonovum
Auteur:
Doe mee:
GitHub geonovum/KP-APIs
Dien een melding in
Revisiehistorie
Pull requests
Rechtenbeleid:

Samenvatting

De API strategie heeft een core, een generieke set regels die op alle overheidsAPI's van toepassing zijn; en diverse extensies die specifiek zijn voor een sector of die nog niet rijp genoeg zijn om in de core te komen.

Deze extensies staan in dit document beschreven. Per extensie is aangegeven of deze stabiel is, of nog in beweging.

Status van dit document

Deze paragraaf beschrijft de status van dit document ten tijde van publicatie. Het is mogelijk dat er actuelere versies van dit document bestaan. Een lijst van Geonovum publicaties en de laatste gepubliceerde versie van dit document zijn te vinden op https://www.geonovum.nl/geo-standaarden/alle-standaarden.

Dit is een definitief concept van de nieuwe versie van de handreiking. Wijzigingen naar aanleiding van consultaties zijn doorgevoerd.

1. Voorbeeld: een stabiele extensie

Dit onderdeel is niet normatief.

Noot

Deze extensie is stabiel bevonden door de werkgroep.

Tekst van de extensie...

2. Voorbeeld: een extensie die nog in ontwikkeling is

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Tekst van de extensie...

3. API Beveiliging

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

API's zijn vanaf elke locatie vanaf het internet te benaderen. Om uitgewisselde informatie af te schermen wordt altijd gebruik gemaakt van een versleutelde verbinding op basis van TLS. Geen uitzonderingen, dus overal en altijd.

Doordat de verbinding altijd is versleuteld is het authenticatiemechanisme eenvoudiger. Hierdoor wordt het mogelijk om eenvoudige toegangstokens te gebruiken in plaats van toegangstokens met encryptie.

API principe: Encrypt connections using at least TLS v1.3

API principe: Allow access to an API only if an API key is provided

Voor meer informatie over beveiliging zie ook hoofdstuk 5.

3.1 Authenticatie en autorisatie

Een REST API mag geen toestand (state) bijhouden. Dit betekent dat authenticatie en autorisatie van een verzoek niet mag afhangen van cookies of sessies. In plaats daarvan wordt elk verzoek voorzien van een token. Binnen het Kennisplatform APIs is gekozen voor OAuth 2.0 als de standaarden voor het autorisatiemechanisme waar dit nodig is, in hoofdstuk 5 is meer informatie te vinden over deze keuze en het gebruik van OAuth 2.0.

API principe: Accept tokens as HTTP headers only

Bij het gebruik van tokens wordt onderscheid gemaakt tussen geauthentiseerde en niet-geauthentiseerde services met de bijhorende headers:

Geauthentiseerd Authorization: Bearer <token>
Niet-geauthentiseerd X-Api-Key: <api-key>

Bij het ontbreken van de juiste headers zijn geen authenticatiedetails beschikbaar en dient de statuscode 403 Forbidden terug te worden gegeven.

API principe: Use OAuth 2.0 for authorisation

Zie ook Het Nederlands profiel OAuth in het hoofdtuk beveiliging voor een nadere uitwerking van de toepassing van OAuth.

API principe: Use PKIoverheid certificates for access-restricted or purpose-limited API authentication

3.1.1 Autorisatiefouten

In een productieomgeving is het wenselijk om voor het (kunnen) autoriseren zo min mogelijk informatie weg te geven. Met dit in het achterhoofd is het advies om voor statuscode 401 Unauthorized, 403 Forbidden en 404 Not Found, de volgende regels te hanteren:

Bestaat de resource? Kan de autorisatie worden bepaald? Geautoriseerd? HTTP statuscode
Ja Ja Ja 20x (200 OK)
Ja Ja Nee 401 Unauthorized
Ja Nee ? 403 Forbidden
Nee Ja Ja 404 Not Found
Nee Ja Nee 403 Forbidden
Nee Nee ? 403 Forbidden

Het idee van deze regels is dat eerst wordt bepaald of de aanroeper (principal) gerechtigd is voor een resource. Is het antwoord ‘nee' of kan dat niet worden bepaald, bijvoorbeeld omdat de resource nodig is om deze beslissing te kunnen nemen en de resource niet bestaat, dan wordt 403 Forbidden teruggegeven. Op deze manier wordt geen informatie teruggegeven over het al dan niet bestaan van een resource aan een niet-geautoriseerde principal.

Een bijkomend voordeel van de strategie om eerst te bepalen of er toegang is, meer ruimte biedt om de access control logica te scheiden van de business code.

3.1.2 Openbare identifiers

Openbaar zichtbare identifiers (ID's), zoals die veelal in URI's van RESTful API's voorkomen, zouden onderliggende mechanismen (zoals een nummergenerator) niet bloot moeten leggen en zeker geen zakelijke betekenis moeten hebben.

UUID

Het wordt aanbevolen om voor resources die een vertrouwelijk karakter hebben het concept van een UUID (Universally-Unique IDentifier) te gebruiken. Dit is een 16-bytes (128-bits) binaire representatie, weergegeven als een reeks van 32 hexadecimale cijfers, in vijf groepen gescheiden door koppeltekens en in totaal 36 tekens (32 alfanumerieke tekens plus vier afbreekstreepjes):

550e8400-e29b-41d4-a716-446655440000

Om te zorgen dat de UUID's korter en gegarandeerd "web-veilig" zijn, is het advies om alleen de base64-gecodeerde variant van 22 tekens te gebruiken. De bovenstaande UUID ziet er dan als volgt uit:

abcdEFh4520juieUKHWgJQ

3.1.3 Blootstellen API-key

De API-key's die standaard worden uitgegeven zijn "unrestricted". Dat wil zeggen dat er geen gebruiksbeperkingen op zitten en ze niet blootgesteld mogen worden via een webapplicatie. Door API-key's zonder gebruiksbeperkingen toe te passen in JavaScript, is er een reële kans op misbruik en quotum-diefstal. Om dit te voorkomen dienen in dit geval zogenaamde "restricted" API-key's te worden uitgegeven en gebruikt.

API principe: Use public API-keys

3.1.4 CORS-policy

Webbrowsers implementeren een zogenaamde "same origin policy", een belangrijk beveiligingsconcept om te voorkomen dat verzoeken naar een ander domein gaan dan waarop het is aangeboden. Hoewel dit beleid effectief is in het voorkomen van aanroepen in verschillende domeinen, voorkomt het ook legitieme interactie tussen een API's en clients van een bekende en vertrouwde oorsprong.

API principe: Use CORS to control access

4. Versionering

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

4.1 Uitfaseren van een major API versie

Major releases van API's zijn altijd backward incompatible. Immers, als een nieuwe release van de API niet tot backward incompatibiliteit leidt, is er geen reden om een hele versie omhoog te gaan en spreken we van een minor release. Op het moment dat er een major release plaatsvindt, is het de bedoeling dat alle (potentiële) clients deze nieuwe versie implementeren.

Omdat we geen clients willen breken kunnen we niet van de een op de andere dag overschakelen van de oude naar de nieuwe versie. Daarom is het noodzakelijk om na de livegang van de nieuwe versie óók de oude versie in de lucht te houden. Omdat we de oude versie niet tot in de eeuwigheid willen blijven onderhouden en juist iedereen willen stimuleren om de nieuwe versie te gaan gebruiken, communiceren we een periode waarin clients de gelegenheid krijgen om hun code aan te passen aan de nieuwe versie. Deze periode noemen we de deprecation periode. De lengte van deze periode kan verschillen per API, vaak is dit zes maanden, maar niet meer dan één jaar. Met het oog op beheersbaarheid is het ten zeerste aan te bevelen om maximaal twee major versies (waarvan één de deprecated variant) naast elkaar te draaien. In deze fase is communicatie met clients van de oude versie cruciaal. De volgende zaken moeten gecommuniceerd worden:

Deze zaken dienen gecommuniceerd te worden via de volgende kanalen:

Stap voor stap betekent dit het volgende:

  1. Lanceren nieuwe versie;
  2. Bepalen deprecation periode;
  3. Schrijven migratieplan;
  4. Communiceren in de API-documentatie van de oude versie;
  5. Deprecation periode communiceren per e-mail, forum en eventuele andere kanalen;
  6. Warning header toevoegen aan responses van de oude versie;
  7. Logs checken om gebruik van de oude versie te monitoren gedurende deprecation periode;
  8. End-point oude versie dichtzetten op geplande datum en feedback monitoren;
  9. Indien er binnen twee weken geen feedback op de oude versie komt kan de oude versie (inclusief docs) verwijderd worden;

4.2 De Warning response header

De Warning header (zie: RFC 7234) die we hier gebruiken heeft warn-code 299 ("Miscellaneous Persistent Warning") en het API endpoint (inclusief versienummer) als de warn-agent van de warning, gevolgd door de warn-text met de human-readable waarschuwing. Voorbeeld:

Waarschuwing: 299 https://service.../api/.../v1 "Deze versie van de API is verouderd en zal uit dienst worden genomen op 2018-02-01. Raadpleeg voor meer informatie hier de documentatie: https://omgevingswet.../api/.../v1".

Gebruikers moeten voldoende tijd hebben om de oude API uit te faseren. Een periode van 6 tot 12 maanden wordt aanbevolen.

API principe: Inform users of a deprecated API actively

5. JSON

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

JavaScript Object Notation (JSON) is een formaat, net zoals XML, om gegevens te serialiseren, op te slaan en te versturen. JSON is het primaire representatieformaat voor API's. Voor meer informatie over JSON zie https://json.org. In tegenstelling tot XML kent JSON een compacte notatie, bijvoorbeeld:

{
"persoon": {
  "naam": "Jan",
  "geboortejaar": 1983
}
}

API principe: JSON first - APIs receive and send JSON

API principe: APIs may provide a JSON Schema

API principe: Support content negotiation

API principe: Check the Content-Type header settings

5.1 Veldnamen in snake_case, camelCase, UpperCamelCase of kebab-case?

Bij veldnamen wordt gebruik gemaakt van camelCase.

API principe: Define field names in in camelCase

5.2 Pretty print

De meeste REST clients en browsers (al dan niet met extensies) kunnen JSON netjes geformatteerd weergeven, ook als de response geen white-space bevat.

API principe: Disable pretty print

5.3 Gebruik geen envelop

Veel API's wikkelen antwoorden in enveloppen zoals hieronder is weergegeven:

{
"persoon": {
  "naam": "Jan",
  "geboortejaar": 1983
}
}

Een toekomstbestendig API is vrij van enveloppen.

API principe: Send a JSON-response without enclosing envelope

5.4 JSON gecodeerde POST, PUT en PATCH payloads

API's ondersteunen minimaal JSON gecodeerde POST, PUT en PATCH payloads. Encoded form data (application/x-www-form-urlencoded) payloads worden niet ondersteund. Wat is het verschil?

Content-Type: application/json resulteert in:

{
"Name": "John Smith",
"Age": 23
}

en Content-Type: application/x-www-form-urlencoded resulteert in: Name=John+Smith&Age=23

API principe: Support JSON-encoded POST, PUT, and PATCH payloads

6. Filteren

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Er wordt gekozen om de basis URL's van resources zo eenvoudig mogelijk te houden. Complexe resultaatfilters, sorteren en geavanceerd zoeken (wanneer dit beperkt blijft tot een enkele resource) worden geïmplementeerd als query-parameters bovenop de basis URL.

Om te filteren wordt gebruik gemaakt van unieke query-parameters die gelijk zijn aan de velden waarop gefilterd kan worden. Als je bijvoorbeeld een lijst met aanvragen wilt opvragen van het eindpunt /aanvragen en deze wilt beperken tot open aanvragen, dan wordt het verzoek GET /aanvragen?status=open gebruikt. Hier is status een veld waarop gefilterd kan worden.

API principe: Use query parameters corresponding to the queryable fields

Dezelfde systematiek kan worden gehanteerd voor geneste properties. Zoals uitgewerkt met een voorbeeld op basis van de volgende collectie:

[{
"id": 1,
"status": "actief",
"overheid": {
  "code": "0000",
  "naam": "Ministerie van BZK"
}
}, {
"id": 2,
"status": "inactief",
"overheid": {
  "code": "9901",
  "naam": "Provincie Gelderland"
}
}]

Alle objecten met de status "actief" kunnen worden gefilterd met /?status=actief. Maar als daarnaast ook op objecten met code "0000" van de overheid gefilterd moeten worden, heeft dit betrekking op een geneste property. Hier kan dan de puntnotatie (zoals bij Javascript) voor worden gebruikt: /?status=actief&overheid.code=0000.

7. Sorteren

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Voor sorteren wordt de query-parameter sorteer gebruikt. Deze query-parameter accepteert een lijst van velden waarop gesorteerd moet worden gescheiden door een komma. Door een minteken ("-") voor de veldnaam te zetten wordt het veld in aflopende sorteervolgorde gesorteerd. Een aantal voorbeelden:

Request Toelichting
GET /aanvragen?sorteer=-prio Haalt een lijst van aanvragen op gesorteerd in aflopende volgorde van prioriteit.
GET /aanvragen?sorteer=-prio,aanvraagDatum Haalt een lijst van aanvragen in aflopende volgorde van prioriteit op. Binnen een specifieke prioriteit, komen oudere aanvragen eerst.

API principe: Use the query parameter sorteer to sort

8. Zoeken

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Soms zijn eenvoudige filters onvoldoende en is de kracht van vrije-tekst zoekmachines nodig. API's ondersteunen dit middels de query-parameter zoek. Het resultaat wordt in dezelfde representatie teruggegeven.

API principe: Use the query parameter zoek for full-text search

Voorbeelden van de combinatie filteren, sorteren en zoeken:

Request Toelichting
GET /aanvragen?sorteer=-wijzigingDatum Haalt een lijst van recente aanvragen op
GET /aanvragen?status=gesloten&sorteer=-wijzigingDatum Haalt een lijst van recent gesloten aanvragen op
GET /aanvragen?zoek=urgent&status=open&sorteer=-prio,aanvraagDatum Haalt een lijst van aanvragen op met de status 'open' en waarin het woord 'urgent' voorkomt, gesorteerd van hoogste naar laagste prioriteit, en daarbinnen op aanvraagdatum van oud naar nieuw

8.1 Wildcards

API's die vrije-tekst zoeken ondersteunen kunnen overweg met twee soorten wildcard karakters:

Bijvoorbeeld, he* zal overeenkomen met elk woord dat begint met he, zoals hek, hemelwaterafvoer, enzovoort. In het geval van he? komt dit alleen overeen met drie letterwoorden die beginnen met he, zoals hek, heg, enzovoort.

API principe: Support both * and ? wildcard characters for full-text search APIs

Hieronder volgen nog een aantal basisregels voor wildcards in zoekopdrachten:

8.2 Aliassen voor terugkerende queries

Om de API ervaring verder te verbeteren is het mogelijk om terugkerende queries als endpoints aan te bieden. Zo kunnen onlangs gesloten aanvragen als volgt worden benaderd:

GET /aanvragen/recent-gesloten

9. Tijdreizen

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Informatie over een resource is onderhevig aan verandering. Tijdreizen is een mechanisme waarmee het mogelijk is om op standaard manier informatie op te vragen over een bepaald moment in de tijd. De drie belangrijkste tijdsmomenten vanuit het perspectief van tijdreizen via een API zijn:

Tijdsmoment Omschrijving
Geldig Dit is een tijdstip waarop de teruggegeven gegevens in de werkelijkheid geldig zijn.
Beschikbaar Dit is een tijdstip waarop geldt dat de teruggegeven gegevens beschikbaar waren via dezelfde API.
In werking (getreden op) Dit is een tijdstip waarop een besluit (of delen daarvan), dan wel de daarvan afgeleide gegevens (zoals de definitie van een begrip) juridische werking krijgt.

Het basisprincipe voor het tijdreizen is daarbij als volgt:

De waarden van de drie query-parameters in de URI zijn als volgt opgebouwd (gebaseerd op RFC3339 / ISO 8601):

Parameter Waarde
geldigOp YYYY-MM-DD
beschikbaarOp YYYY-MM-DDThh:mm:ss.s
inWerkingOp YYYY-MM-DD
Waarde Betekenis
YYYY Viercijferig jaar
MM Tweecijferige maand (01 = januari, enz.)
DD Tweecijferige dag van de maand (01 tot en met 31)
hh Twee cijfers van het uur (00 tot 23) (am / pm niet toegestaan)
mm Twee cijfers van de minuut (00 tot en met 59)
ss twee cijfers van de seconden (00 tot en met 59)
s Eén of meer cijfers die een decimale fractie van een seconde vertegenwoordigen

9.1 Mate van ondersteuning

Tijdreizen is een optioneel mechanisme met optionele features. Het kan dus voorkomen dat:

Bij de verwerking van tijdreisvragen dient de afnemer geïnformeerd te worden over ongeldige/niet ondersteunde verzoeken. In de volgende specifieke gevallen wordt de bijbehorende statuscode en toelichting teruggegeven:

Wat wordt niet ondersteund? HTTP statuscode en toelichting
Tijdreizen HTTP/1.1 403 Content-Type: application/problem+json Content-Language: nl { "type": ".../id/<c>/parameter/TijdreizenNietOndersteund", "title": "{Het verzoek is begrepen, maar de tijdreisparameters worden niet ondersteund}", "status": 403, "detail": "{Tijdreizen wordt niet ondersteund, verwijder alle tijdreisparameters}", "instance": "/resource/#id"}
Inwerkingtreding HTTP/1.1 403 Content-Type: application/problem+json Content-Language: nl { "type":".../id/<c>/parameter/InWerkingTredingNietOndersteund’", "title": "{Het verzoek is begrepen, maar de tijdreisparameter ‘inWerkingOp’ wordt niet ondersteund}", "status": 403, "detail": "{Tijdreizen met een inwerkingstredingsmoment wordt niet ondersteund, verwijder de parameter ‘inWerkingOp’}", "instance": "/resource/#id"}
Toekomst HTTP/1.1 403 Content-Type: application/problem+json Content-Language: nl { "type": ".../id/<c>/parameter/ToekomstNietOndersteund", "title": "{Het verzoek is begrepen, maar tijdreisparameters mogen geen tijdstip in de toekomst bevatten” }", "status": 403, "detail": "{Tijdreizen naar de toekomst wordt niet ondersteund, verwijder de tijdreisparameters of gebruik een tijdstip in het verleden}", "instance": "/resource/#id"}

9.2 Robuustheid

Bij de verwerking van tijdreisvragen dient ook rekening te worden gehouden met het ontbreken van historie en globale vragen in een resource-collectie. In volgende specifieke gevallen wordt de bijbehorende statuscode en toelichting teruggegeven:

Soort verzoek HTTP statuscode en toelichting
Specifieke resource HTTP/1.1 404 Content-Type: application/problem+json Content-Language: nl {"type": ".../id/<c>/BestaatNiet", "title": "De gevraagde versie bestaat niet.", "status": 404, "detail": "Versie 2017-01-01 van regeling 123 bestaat niet.", "instance": "/regelingen/v1/123?geldigOp=2017-01-01"}
Resource-collectie GET .../regelingen/v1?beschikbaarOp=2017-01-01T00:00:00 HTTP/1.1 200 Content-Type: application/json+hal {"_links": {"self": {"href": "/r-n/v1/123?beschikbaarOp=2017-01-01T00:00:00" }, "items": [ { "href": "/r-n/v1/123?beschikbaarOp=2017-01-01T00:00:00" }, { "href": "/r-n/v1/456?beschikbaarOp=2017-01-01T00:00:00" }, { "href": "/r-n/v1/789?beschikbaarOp=2017-01-01T00:00:00" }] } }

10. GEO-ondersteuning

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

REST API's voor het werken met geometrieën kunnen een filter aanbieden op basis van geografische gegevens. Het is hierbij belangrijk om een onderscheid te maken tussen een geometrie in het resultaat (response) en een geografische filter in de aanroep (request). Het is immers niet vanzelfsprekend dat als iemand wil weten in welk perceel ik hij/zij zich momenteel bevindt, dat ook de geometrie in de response wordt teruggegeven; een naam of nummer kan dan al voldoende zijn.

API principe: Support GeoJSON for GEO APIs

Voor GEO API's wordt bij voorkeur de standaard GeoJSON [rfc8142] gebruikt.

10.1 Resultaat (response)

In een JSON API wordt een geometrie teruggegeven als GeoJSON. We kiezen er voor om dit binnen de application/json te ‘wrappen' in een apart GeoJSON object.

API principe: Include GeoJSON as part of the embedded resource in the JSON response

10.2 Aanroep (requests)

Een geografisch filter kan erg complex en groot zijn. Het is daarom een best practice om dergelijke complexe vragen niet in de request URI, maar in de body mee te sturen. Omdat GET geen payload mag hebben (hoewel sommige clients dit wel ondersteunen) moet hier dus een andere methode voor gebruikt worden.

In analogie met API's zoals die van Elasticsearch, is een POST naar een apart endpoint de juiste weg om te bewandelen. Het onderstaande voorbeeld doet een zogenaamde GEO-query naar alle panden waarin het veld _geo (er kunnen ook andere velden zijn, zoals hoofdgeometrie, binnenOmtrek, buitenOmtrek, etc.) het GeoJSON object (in dit geval een Point, dus één coördinaat) bevat:

// POST /api/v1/panden/_zoek met request body:
{
"_geo": {
  "contains": {
    "type": "Point",
    "coordinates": [5.9623762, 52.2118093]
  }
}
}

API principe: Provide a POST endpoint for GEO queries

Omdat we ons met het geo endpoint beperken tot een GEO-query en we wellicht ook gecombineerde queries willen doen is het sterk aan te bevelen om een generiek query endpoint in te richten:

// POST /api/v1/panden/_zoek met request body:
{
"_geo": {
  "contains": {
    "type": "Point",
    "coordinates": [5.9623762, 52.2118093]
  }
},
"status": "Actief"
}

API principe: Support mixed queries at POST endpoints

Naast contains kan er ook intersects (snijdt) of within (valt binnen) als operators gebruikt worden. De benamingen van deze operators komen uit de GEO-wereld en die willen we niet opnieuw uitvinden. Zie voor meer details: https://www.w3.org/TR/sdw-bp/#entity-level-links

Omdat we voor de geometrie een GeoJSON object gebruiken hoeven we hier geen syntax meer voor te verzinnen.

API principe: Put results of a global spatial query in the relevant geometric context

In het volgende voorbeeld wordt aangegeven hoe dit kan worden gerealiseerd:

// POST /api/v1/_zoek:
{
"_embedded": {
  "results": [{
    "type": "enkelbestemming",
    "_links": {
      "self": {
        "title": "Enkelbestemming 1234",
        "href": "/enkelbestemmingen/1234"
      }
    }
  }, {
    "type": "dubbelbestemming",
    "_links": {
      "self": {
        "title": "Dubbelbestemming 8765",
        "href": "/dubbelbestemmingen/8765"
      }
    }
  }]
}
}

10.3 CRS-negotiation

Het default CRS (Coordinate Reference System) van GeoJSON is WGS84. Dit is het globale coördinatenstelsel dat overal ter wereld redelijk goed bruikbaar is, maar vanwege het gebruikte model van de aarde en de verschuiving van de tektonische platen minder nauwkeurig is dan lokale coördinatenstelsels zoals ETRS89 (EPSG:4258, Europees) of RD (EPSG:28992, Nederlands).

Omdat de meeste client-bibliotheken met WGS84 werken, schrijft de W3C/OGC werkgroep "Spatial Data on the Web" voor om dit standaard te ontsluiten. Dit kan direct op een kaart geplot worden zonder moeilijke transformaties. De API-strategie voorziet hierin door naast ETRS89 en RD ook WGS84 of Web Mercator (EPSG:3857, voor rasterdata) te ondersteunen.

API principe: Use ETRS89 as the preferred coordinate reference system (CRS)

Het is mogelijk om het CRS voor vraag en antwoord via headers afzonderlijk te specificeren. Hierin zijn vervolgens drie opties (met voorgeschreven projecties) voorhanden: RD, ETRS89 en WGS84.

Een nadere opsomming van de uitgangspunten voor het CRS:

API principe: Pass the coordinate reference system (CRS) of the request and the response in the headers

De hier genoemde headers zijn puur bedoeld voor de onderhandeling tussen de client en de server. Afhankelijk van de toepassing zal naast de geometrieën ook specifieke metadata onderdeel vormen van het antwoord, bijvoorbeeld de oorspronkelijke realisatie inclusief een inwindatum.

Vraag en antwoord kunnen op een ander coördinatensysteem zijn gebaseerd. Hiervoor wordt het HTTP-mechanisme voor content negotiation gebruikt. Het CRS van de geometrie in de vraag (request body) wordt aangeduid met de header Content-Crs.

HTTP header Waarde Toelichting
Content-Crs EPSG:3856 WGS84, Wereld (Web-Mercator-projectie)
Content-Crs EPSG:4258 ETRS89, Europees
Content-Crs EPSG:28992 RD, Nederlands

Het gewenste CRS voor de geometrie in het antwoord (response body) wordt aangeduid met de header Accept-Crs.

HTTP header Waarde Toelichting
Accept-Crs EPSG:3856 WGS84, Wereld (Web-Mercator-projectie)
Accept-Crs EPSG:4258 ETRS89, Europees
Accept-Crs EPSG:28992 RD, Nederlands

10.4 CRS-transformatie

Voor het transformeren tussen coördinaatreferentiesystemen is binnen de Rijksoverheid software met een keurmerk beschikbaar.

API principe: Use content negotiation to serve different CRSs

11. Paginering

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Voor paginering wordt voor het media type 'application/hal+json' aangesloten op Hypertext Application Language (HAL). Aan geretourneerde objecten worden twee gereserveerde velden _links (verplicht) en _embedded (optioneel) toegevoegd. Deze velden vertegenwoordigen respectievelijk hyperlinks en embedded resources.

Hier is een voorbeeld van een JSON+HAL representatie:

{
"_links": {
  "self": {
    "href": "https://.../api/registratie/v1/aanvragen?pagina=3"
  },
  "first": {
    "href": "https://.../api/registratie/v1/aanvragen"
  },
  "prev": {
    "href": "https://.../api/registratie/v1/aanvragen?pagina=2"
  },
  "next": {
    "href": "https://.../api/registratie/v1/aanvragen?pagina=4"
  },
  "last": {
    "href": "https://.../api/registratie/v1/aanvragen?pagina=5"
  }
},
"id": "https://.../api/registratie/v1/aanvragen/12",
"naam": "Mijn dakkapel",
"samenvatting": "Ik wil een dakkapel bouwen!",
"_embedded": {
  "aanvrager": {
    "naam": "Bob"
  }
}
}

Indien het "plain" JSON, GeoJSON of iets anders dan HAL betreft zijn er geen _links. Deze kunnen dan opgenomen worden in de link response headers. Naast de representatie wordt de volgende metadata teruggegeven als HTTP headers.

HTTP header Toelichting
X-Total-Count (optioneel) Totaal aantal resultaten
X-Pagination-Count (optioneel) Totaal aantal pagina's
X-Pagination-Page (optioneel) Huidige pagina
X-Pagination-Limit (optioneel) Aantal resultaten per pagina

Bij grote datasets kunnen de berekeningen voor X-Total-Count en X-Pagination-Count behoorlijke impact hebben op de performance, voornamelijk als er niet of nauwelijks gefilterd wordt.

API principe: Use JSON+HAL with media type application/hal+json for pagination

Alle links in HAL zijn absoluut. Dit in verband met mogelijke externe links (naar andere endpoints, linked-data resources, etc.) en eenvoudigere navigatie door clients die dan niet zelf de URL hoeven op te bouwen.

12. Caching

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Voor sommige resources kan het nuttig zijn om caching toe te passen. HTTP biedt voor caching standaard mechanismes. Door deze mechanismes te gebruiken wordt gebruik gemaakt van standaard caching oplossingen van de client en in de infrastructuur.

Het enige wat nodig is om hiervan gebruik te maken is:

Er zijn 2 manieren om caching te realiseren: ETag en Last-Modified.

12.1 ETag

Een ETag (Entity Tag) is een hashcode of checksum van een resource. Als de resource wijzigt ontstaat een andere ETag. Een ETag is dus uniek voor een bepaalde versie van een resource. De ETag wordt als de HTTP header ETag teruggegeven met de resource. De afnemer cached de resource en de ETag. Als de afnemer dezelfde resource opvraagt dan stuurt deze de ETag mee in de HTTP header If-None-Match. De server controleert of de ETag in de HTTP header If-None-Match gelijk is aan de eigen ETag. Indien dit het geval geeft de server een HTTP statuscode 304 Not Modified terug. De afnemer laadt dan de resource uit de eigen cache. Dit gaat alleen op over clientside caching, dus is alleen van toepassing als de client ook daadwerkelijk de request headers meestuurt, anders altijd een 200 OK.

12.2 Last-Modified

Dit werkt in principe net als ETag, behalve dat het gebruik maakt van tijdstempels. De HTTP header Last-Modified bevat een tijdstempel in het RFC 1123 formaat die wordt gevalideerd tegen de HTTP header If-Modified-Since. De server controleert of de resource gewijzigd is sinds het aangegeven tijdstip. Indien dit niet het geval is dan geeft de server een HTTP statuscode 304 Not Modified terug. De afnemer laadt dan de resource uit de eigen cache. Dit gaat alleen op over client-side caching, dus is alleen van toepassing als de client ook daadwerkelijk de request headers meestuurt, anders altijd een 200 OK.

API principe: Apply caching to improve performance

13. Begrenzingen

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

API's beperken het aantal verzoeken dat per tijdsperiode gedaan kan worden, om te voorkomen dat de servers overbelast worden om een hoog serviceniveau te garanderen. API's kunnen een bevragingslimiet (quota) per maand bijhouden en die wordt afgedwongen per tijdsperiode van 60 seconden.

HTTP headers worden gebruikt om de bevragingslimit naar de gebruiker te communiceren.

HTTP header Toelichting
X-Rate-Limit-Limit Geeft aan hoeveel verzoeken een applicatie mag doen per tijdsperiode
X-Rate-Limit-Remaining Geeft aan hoeveel verzoeken nog gedaan kunnen worden in de huidige tijdsperiode
X-Rate-Limit-Reset Geeft aan hoeveel seconden over zijn in de huidige tijdsperiode

API principe: Apply rate limiting

[rfc6585] introduceert een HTTP statuscode 429 Too Many Requests die wordt gebruikt om het overschrijden van het aantal verzoeken te melden aan de gebruiker.

API principe: Provide rate limiting information

14. Foutafhandeling

Dit onderdeel is niet normatief.

Waarschuwing

Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.

Net als een webpagina een bruikbare foutmelding toont aan bezoekers als een fout optreedt, moet een API een bruikbare foutmelding in een bekend en verwerkbaar formaat teruggeven. De representatie van een fout is niet anders dan de representatie van een willekeurige resource alleen met een eigen set van velden.

De API moet altijd zinvolle HTTP statuscodes teruggeven. HTTP statuscodes zijn opgesplitst in twee categorieën:

Een JSON representatie van een fout moet een aantal zaken bevatten om een ontwikkelaar, beheerder en eindgebruiker te helpen:

De basis voor deze standaardformaten is [rfc7807]. Een JSON-representatie van een fout ziet er als volgt uit:

{
"type": "URI: https://content.omgevingswet.overheid.nl/id/<c>[/{categorie}]/{fout}",
"title": "Hier staat wat er is misgegaan",
"status": 401,
"detail": "Meer details over de fout staan hier",
"instance": "urn:uuid:ebd2e7f0-1b27-11e8-accf-0ed5f89f718b" // De fout-instantie  
}

Validatiefouten voor POST, PUT en PATCH verzoeken worden per veld gespecificeerd. De volledige lijst met fouten wordt in één keer teruggegeven. Dit wordt opgezet met een vast hoofdniveau en foutcode voor validatiefouten en extra foutvelden met gedetailleerde fouten per veld.

Dit ziet er dan als volgt uit:

{
"type": "https://content.omgevingswet.overheid.nl/id/<c>/ValidatieFout",
"title": "Hier staat wat er is misgegaan…",
"status": 400,
"invalid-params": [{
  "type": "https://content.omgevingswet.overheid.nl/id/<c>/validatie/Voornaam",
"name": "voornaam",
"reason": "De voornaam mag geen speciale karakters bevatten."
}, {
"type": " https://content.../<c>/fouten/validatie/Wachtwoord",
"name": "wachtwoord",
"reason": "Het wachtwoord is verplicht."
}],
"instance": "urn:uuid:4017fabc-1b28-11e8-accf-0ed5f89f718b" // De fout-instantie
}

API principe: Use default error handling

14.1 HTTP statuscodes

HTTP definieert een hele reeks gestandaardiseerde statuscodes die gebruikt dienen te worden door API's. Deze helpen de gebruikers van de API's bij het afhandelen van fouten.

API principe: Use the required HTTP status codes

Samenvatting HTTP-operaties in combinatie met de primaire HTTP statuscodes:

Operatie CRUD Gehele collectie (bijvoorbeeld /resource)
Specifieke item (bijvoorbeeld /resource/\<id>)
POST Create 201 (Created), HTTP header Location met de URI van de nieuwe resource (/resource/\<id>)
405 (Method Not Allowed), 409 (Conflict) als de resource al bestaat
GET Read 200 (OK), lijst van resources. Gebruik pagineren, filteren en sorteren om het werken met grote lijsten te vereenvoudigen
200 (OK) enkele resource, 404 (Not Found) als ID niet bestaat of ongeldig is
PUT Update 405 (Method Not Allowed), behalve als het de bedoeling is om elke resource in een collectie te wijzigen of vervangen
409 als een wijziging niet mogelijk is vanwege de huidige toestand van de instantie
200 (OK) of 204 (No Content), 404 (Not Found) als ID niet bestaat of ongeldig is
PATCH Update 405 (Method Not Allowed), behalve als het de bedoeling is de gehele collectie te vervangen.
409 als een wijziging niet mogelijk is vanwege de huidige toestand van de instantie.
200 (OK) of 204 (No Content), 404 (Not Found) als ID niet bestaat of ongeldig is
DELETE Delete 405 (Method Not Allowed), behalve als het de bedoeling is de gehele collectie te verwijderen
200 (OK) of 404 (Not Found) als ID niet bestaat of ongeldig is

Hieronder een korte lijst met een beschrijving van de HTTP statuscodes die minimaal worden toegepast:

HTTP statuscode Toelichting
200 OK Reactie op een succesvolle GET, PUT, patch of DELETE. Ook geschikt voor POST die niet resulteert in een creatie
201 Created Reactie op een POST die resulteert in een creatie. Moet worden gecombineerd met een locatie-header die wijst naar de locatie van de nieuwe resource
204 No Content Reactie op een succesvol verzoek die geen inhoud zal teruggeven (zoals een DELETE)
304 Not Modified Gebruikt wanneer HTTP caching headers worden toegepast
400 Bad Request Het verzoek is onjuist, bijvoorbeeld als het verzoek (body) niet kan worden geïnterpreteerd
401 Unauthorized Als er geen of ongeldige authenticatie details worden verstrekt. Ook handig om een authenticatie-venster te tonen als de API wordt gebruikt vanuit een browser
403 Forbidden Als de authenticatie gelukt is maar de geverifieerde gebruiker geen toegangsrechten heeft voor de resource
404 Not Found Wanneer een niet-bestaande resource is opgevraagd
405 Method Not Allowed Wanneer een HTTP-methode wordt gebruikt die niet is toegestaan voor de geauthentiseerde gebruiker
406 Not Acceptable Wordt teruggegeven als het gevraagde formaat niet ondersteund wordt (onderdeel van content negotiation)
409 Conflict Het verzoek kon ik niet worden verwerkt als het gevolg van een conflict met de huidige toestand van de resource
410 Gone Geeft aan dat de resource niet langer op het eindpunt beschikbaar is. Nuttig als een overkoepelend antwoord voor oude API versies
412 Precondition Failed De preconditie die wordt gegeven door één of meer velden in de request-header, ontbraken of zijn na validatie op de server afgekeurd
415 Unsupported Media Type Als een verkeerd content-type als onderdeel van het verzoek werd meegegeven
422 Unprocessable Entity Gebruikt voor een verzoek (body) dat correct is maar dat de server niet kan verwerken
429 Too Many Requests Wanneer een aanvraag wordt afgewezen als het aantal verzoeken per tijdsperiode is overschreden
500 Internal Server Error Wanneer een onverwachte fout optreedt en het beantwoorden van het verzoek wordt verhinderd
503 Service Unavailable Wordt gebruikt als de API niet beschikbaar is (bijv. door gepland onderhoud)
-->

15. Supplements

Dit onderdeel is niet normatief.

15.1 API Principles

15.1.1 API-01: Operations are Safe and/or Idempotent

Operations of an API are guaranteed to be safe and/or idempotent if that has been specified.

15.1.2 API-02: Do not maintain state information at the server

The client state is tracked fully at the client.

15.1.3 API-03: Only apply default HTTP operations

A RESTful API is an application programming interface that supports the default HTTP operations GET, PUT, POST, PATCH and DELETE.

15.1.4 API-04: Define interfaces in Dutch unless there is an official English glossary

Define resources and the underlying entities, fields and so on (the information model ad the external interface) in Dutch. English is allowed in case there is an official English glossary.

15.1.5 API-05: Use plural nouns to indicate resources

Names of resources are nouns and always in the plural form, e.g. aanvragen , activiteiten, vergunningen, even when it applies to single resources.

15.1.6 API-06: Create relations of nested resources within the endpoint

Preferrably, create relation within the endpoint if a relation can only exist with another resource (nested resource). In that case, the dependent resource does not have its own endpoint.

15.1.7 API-09: Implement custom representation if supported

Provide a comma-separated list of field names using the query parameter fields te retrieve a custom representation. In case non-existent field names are passed, a 404 Bad Request error message is returned.

15.1.8 API-10: Implement operations that do not fit the CRUD model as sub-resources

"Operations that do not fit the CRUD model are implemented as follows:

  • Treat an operation as a sub-resource.

  • Only in exceptional cases, an operator is implemented as an endpoint."

15.1.9 API-11: Encrypt connections using at least TLS v1.3

Encrypt connections using at least TLS v1.3. Use TLS v1.2 as a fall-back option only. In case of access restrictions use two-way TLD. Since the connection is always encrypted, the authentication method is straightforward. This allows the application of basic authentication tokens instead of encrypted authentication tokens.

15.1.10 API-12: Allow access to an API only if an API key is provided

Preferrably, APIs should require at least a sign-up process that involves accepting its fair use policy before an API key is issued.

15.1.11 API-13: Accept tokens as HTTP headers only

There is an inherent security issue when passing tokens as a query parameter, because most Web servers store query parameters in the server logs.

15.1.12 API-14: Use OAuth 2.0 for authorisation

A RESTful API should not maintain state. A token has to be sent for each request. OAuth 2.0 is the recommended standard. Chapter Beveiliging contains further information.

15.1.13 API-15: Use PKIoverheid certificates for access-restricted or purpose-limited API authentication

In the case of APIs that have access-restrictions or purpose-limitations, additional authentication based on PKIoverheid certificates and mutual TLS authentication should be provided.

15.1.14 API-16: Use OAS 3.0 for documentation

Publish specifications (documentation) as Open API Specification (OAS) 3.0 or higher.

15.1.15 API-17: Publish documentation in Dutch unless there is existing documentation in English or there is an official English glossary available

Publish API documentation in Dutch. You may refer to existing documentation in Engelish and in case there is an official English glossary avaialble.

15.1.16 API-18: Include a deprecation schedule when publishing API changes

API changes and a deprecation schedule should be published not only as a changelog on a publicly available blog but also through a mailing list.

15.1.17 API-19: Allow for a maximum 1 year transition period to a new API version

Old and new versions (maximum 3) of an API should be provided concurrently for a limited, maximum 1 year transition period.

15.1.18 API-20: Include the major version number only in ihe URI

The URI of an API should include the major version number only. The minor and patch version numbers are in the response header of the message. Minor and patch versions have no impact on existing code, but major version do.

15.1.19 API-21: Inform users of a deprecated API actively

Using the Warning response header in all responses of the deprecated APIs, users are informed of the deprecation and upcoming removal date.

15.1.20 API-22: JSON first - APIs receive and send JSON

APIs receive and send JSON.

15.1.21 API-23: APIs may provide a JSON Schema

APIs may support JSON Schema (<http: json-schema.org)="">to allow and facilitate validation.

15.1.22 API-24: Support content negotiation

Besides JSON, APIs should support other representations as XML and RDF using the default HTTP content negotiation mechanism. In case the requested format cannot be provided, a 406 ot Acceptable response is sent.

15.1.23 API-25: Check the Content-Type header settings

Check the Content-Type header is application/json or another supported content types, otherwise send the HTTP status code 415 Unsupported Media Type.

15.1.24 API-26: Define field names in in camelCase

Define field names in a such a way that the first word starts with a lower case and subsequent words start with a capital letter, with no intervening spaces or punctiation.

15.1.25 API-27: Disable pretty print

The assumption is that REST clients and Web browsers (either with or without add-ons or extensions) can pretty print JSON.

15.1.26 API-28: Send a JSON-response without enclosing envelope

By default, don't apply an envelope.

15.1.27 API-29: Support JSON-encoded POST, PUT, and PATCH payloads

APIs support at least JSON-encoded POST, PUT, and PATCH payloads. Encoded form data (application/x-www-form-urlencoded) is not supported.

15.1.28 API-30: Use query parameters corresponding to the queryable fields

Use uniqe query parameters that correspond to the fields that can be queried.

15.1.29 API-31: Use the query parameter sorteer to sort

Specify the comma-separated field to sort using the generic query parameter sorteer. Placing a minus sign (-) in front of a field name, the field is sorted in descending order.

15.1.31 API-33: Support both * and ? wildcard characters for full-text search APIs

Full-text search APIs should support two wildcard characters:

  • * Matches zero or more (non-space) characters

  • ? Matches exactly one (non-space) character

15.1.32 API-34: Support GeoJSON for GEO APIs

Preferrably, GEO APIs should support the GeoJSON standard (RFC-7946).

15.1.33 API-35: Include GeoJSON as part of the embedded resource in the JSON response

In case a JSON (application/json) response contains a geometry, represent it in the same way as the geometry object of GeoJSON (RFC-7946):

{
 "type": "Point",
 "coordinates": [125.6, 10.1]
}

15.1.34 API-36: Provide a POST endpoint for GEO queries

Spatial queries are sent in a POST to a dedicated endpoint.

15.1.35 API-37: Support mixed queries at POST endpoints

Mixed queries may include both spatial and property queries.

15.1.36 API-38: Put results of a global spatial query in the relevant geometric context

In case of a global query /api/v1/_zoek, results should be placed in the relevant geometric context, because results from different collections are retrieved. Express the name of the collection to which the results belongs in the singular form using the property type.

15.1.37 API-39: Use ETRS89 as the preferred coordinate reference system (CRS)

General usage of the European ETRS89 coordinate reference system (CRS) is preferable, but is not necessarily the default CRS. Hence, the CRS has to be explicitly included in each request.

15.1.38 API-40: Pass the coordinate reference system (CRS) of the request and the response in the headers

The coordinate reference system (CRS) for both the request and the response are passed as part of the request headers and reponse headers. In case this header is missing, send the HTTP status code 412 Precondition Failed.

15.1.39 API-41: Use content negotiation to serve different CRSs

The CRS for the geometry in the response body is defined using the Accept-Crs header. In case the API does not support the requested CRS, send the HTTP status code 406 Not Acceptable.

15.1.40 API-42: Use JSON+HAL with media type application/hal+json for pagination

Add two reserved fields _links (required) and _embedded (optional) to the representation. Pass pagination meta data as HTTP headers.

15.1.41 API-43: Apply caching to improve performance

For caching apply the default HTTP caching mechanisms using a few additional HTTP headers (ETag or Last-Modified) and functionality to determine wether a few specific HTTP headers are supplied (If-None-Match or If-Modified-Since).

15.1.42 API-44: Apply rate limiting

To prevent server overload and the guarantee a high service level, apply rate limiting to API requests.

15.1.43 API-45: Provide rate limiting information

Use the HTTP header X-Rate-Limit to inform users of rate limits. In case the rate limits are exceeded, send the HTTP status code 429 Too Many Requests.

15.1.44 API-46: Use default error handling

API support the default error messages of the HTTP 400 and 500 status code ranges, including the parsable JSON representation (RFC-7807).

15.1.45 API-47: Use the required HTTP status codes

APIs should at least support the following HTTP status codes: 200, 201, 204, 304, 400, 401, 403, 405, 406, 409, 410, 415, 422, 429, 500, and 503.

15.1.46 API-48: Leave off trailing slashes from API endpoints

URIs to retrieve collections of resources or individual resources don't include a trailing slash. A resource is only available at one endpoint/path. Resource paths end without a slash.

15.1.47 API-49: Use public API-keys

In JavaScript, only use restricted API-keys, linked to specific characteristics of the client-application (web application or mobile application), e.g. a clientId and/or referring URL.

15.1.48 API-50: Use CORS to control access

Check the domain of the incoming request and generate the response header depending on whether this domain may send requests or not (whitelist). In that case, only add this particular domain to the response header Access-Control-Allow-Origin.

**NOTE: It is technically possible to pass a wildcard ("*") in the response header Access-Control-Allow-Origin to allow all sources. However, this is malpractice!**

15.1.49 API-51: Publish OAS at the base-URI in JSON-format

Publish up-to-date documentation in the Open API Specification (OAS) at the publicly accessible root endpoint of the API in JSON format:

https://service.omgevingswet.overheid.nl/publiek/catalogus/api/raadplegen/v1

Makes the OAS relevant to v1 of the API available.

Thus, the up-to-date documentation is linked to a unique location (that is always concurrent with the features available in the API).

A. Referenties

A.1 Informatieve referenties

[rfc6585]
Additional HTTP Status Codes. M. Nottingham; R. Fielding. IETF. April 2012. Proposed Standard. URL: https://httpwg.org/specs/rfc6585.html
[rfc7807]
Problem Details for HTTP APIs. M. Nottingham; E. Wilde. IETF. March 2016. Proposed Standard. URL: https://tools.ietf.org/html/rfc7807
[rfc8142]
GeoJSON Text Sequences. S. Gillies. IETF. April 2017. Proposed Standard. URL: https://tools.ietf.org/html/rfc8142