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.
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.
Dit onderdeel is niet normatief.
Deze extensie is stabiel bevonden door de werkgroep.
Tekst van de extensie...
Dit onderdeel is niet normatief.
Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.
Tekst van de extensie...
Dit onderdeel is niet normatief.
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: Allow access to an API only if an API key is provided
Voor meer informatie over beveiliging zie ook hoofdstuk 5.
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.
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.
Zie ook Het Nederlands profiel OAuth in het hoofdtuk beveiliging voor een nadere uitwerking van de toepassing van OAuth.
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.
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
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.
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.
Dit onderdeel is niet normatief.
Deze extensie is nog in ontwikkeling en kan elk moment wijzigen.
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:
Warning
response header in alle responses van de oude API.Stap voor stap betekent dit het volgende:
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.
Dit onderdeel is niet normatief.
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
}
}
snake_case
, camelCase
, UpperCamelCase
of kebab-case
?Bij veldnamen wordt gebruik gemaakt van camelCase
.
De meeste REST clients en browsers (al dan niet met extensies) kunnen JSON netjes geformatteerd weergeven, ook als de response geen white-space bevat.
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
POST
, PUT
en PATCH
payloadsAPI'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
, andPATCH
payloads
Dit onderdeel is niet normatief.
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
.
Dit onderdeel is niet normatief.
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. |
Dit onderdeel is niet normatief.
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 |
API's die vrije-tekst zoeken ondersteunen kunnen overweg met twee soorten wildcard karakters:
*
Komt overeen met nul of meer (niet-spatie) karakters?
Komt precies overeen met één (niet-spatie) karakterBijvoorbeeld, 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:
m*??
komt overeen met woorden die beginnen met m
en drie of meer tekens hebben.%20
) worden gebruikt als woordscheiding en wildcardmatching werkt alleen binnen een enkel woord. Bijvoorbeeld, r*te*
komt overeen met de r
uim
te
lijk
, maar niet met r
uimte
te
kort
.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
Dit onderdeel is niet normatief.
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 |
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"} |
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" }] } } |
Dit onderdeel is niet normatief.
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.
Voor GEO API's wordt bij voorkeur de standaard GeoJSON [rfc8142] gebruikt.
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
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]
}
}
}
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"
}
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"
}
}
}]
}
}
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:
52.255023450
)195427.5200 311611.8400
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 |
Voor het transformeren tussen coördinaatreferentiesystemen is binnen de Rijksoverheid software met een keurmerk beschikbaar.
API principe: Use content negotiation to serve different CRSs
Dit onderdeel is niet normatief.
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.
Dit onderdeel is niet normatief.
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.
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
.
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
.
Dit onderdeel is niet normatief.
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 |
[rfc6585] introduceert een HTTP statuscode 429 Too Many Requests
die wordt gebruikt om het overschrijden van het aantal verzoeken te melden aan de gebruiker.
Dit onderdeel is niet normatief.
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
}
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.
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) |
Dit onderdeel is niet normatief.
Operations of an API are guaranteed to be safe and/or idempotent if that has been specified.
The client state is tracked fully at the client.
A RESTful API is an application programming interface that supports the default HTTP operations GET, PUT, POST, PATCH and DELETE.
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.
Names of resources are nouns and always in the plural form, e.g. aanvragen , activiteiten, vergunningen, even when it applies to single resources.
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.
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.
"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."
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.
Preferrably, APIs should require at least a sign-up process that involves accepting its fair use policy before an API key is issued.
There is an inherent security issue when passing tokens as a query parameter, because most Web servers store query parameters in the server logs.
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.
Publish specifications (documentation) as Open API Specification (OAS) 3.0 or higher.
Publish API documentation in Dutch. You may refer to existing documentation in Engelish and in case there is an official English glossary avaialble.
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.
Old and new versions (maximum 3) of an API should be provided concurrently for a limited, maximum 1 year transition period.
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.
Using the Warning
response header in all responses of the deprecated APIs,
users are informed of the deprecation and upcoming removal date.
APIs receive and send JSON.
APIs may support JSON Schema (<http: json-schema.org)="">to allow and facilitate validation.
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.
Check the Content-Type
header is application/json
or another supported
content types, otherwise send the HTTP status code 415 Unsupported Media Type
.
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.
The assumption is that REST clients and Web browsers (either with or without add-ons or extensions) can pretty print JSON.
By default, don't apply an envelope.
POST
, PUT
, and PATCH
payloadsAPIs support at least JSON-encoded POST
, PUT
, and PATCH
payloads. Encoded
form data (application/x-www-form-urlencoded
) is not supported.
Use uniqe query parameters that correspond to the fields that can be queried.
sorteer
to sortSpecify 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.
zoek
for full-text searchAPIs support full-text searching using the query parameter zoek
.
*
and ?
wildcard characters for full-text search APIsFull-text search APIs should support two wildcard characters:
*
Matches zero or more (non-space) characters
?
Matches exactly one (non-space) character
Preferrably, GEO APIs should support the GeoJSON standard (RFC-7946).
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]
}
POST
endpoint for GEO queriesSpatial queries are sent in a POST
to a dedicated endpoint.
POST
endpointsMixed queries may include both spatial and property queries.
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
.
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.
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
.
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
.
application/hal+json
for paginationAdd two reserved fields _links
(required) and _embedded
(optional) to the
representation. Pass pagination meta data as HTTP headers.
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
).
To prevent server overload and the guarantee a high service level, apply rate limiting to API requests.
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
.
API support the default error messages of the HTTP 400 and 500 status code ranges, including the parsable JSON representation (RFC-7807).
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.
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.
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.
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!**
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).