Reference
HTTP API
Resources, parameters, responses and limits of the API bagkit serve offers.
bagkit serve 8080 --database bag.sqlitecurl 'http://localhost:8080/v1/adressen?postcode=9902HA&huisnummer=1'The API lives under /v1 and follows the Dutch government’s API Design Rules (NLGov ADR 2.2) and their Geospatial module. As those rules ask, it is in Dutch: resources, fields, parameters and messages use the BAG’s terms, named as in Kadaster’s BAG API Individuele Bevragingen where it has them. The server describes itself in an OpenAPI 3.0 document at /v1/openapi.json, which tools such as Swagger UI and client generators read; it is the authoritative contract, and this page explains it.
Resources
Section titled “Resources”All resources answer GET and HEAD, and return JSON.
| Resource | Returns |
|---|---|
/v1 |
The API version, the snapshot the server reads, and a link to the description |
/v1/openapi.json |
The OpenAPI description |
/v1/crss |
The supported coordinate reference systems |
/v1/adressen |
Adressen with a postcode, narrowed by huisnummer, huisletter and toevoeging |
/v1/adressen/{nummeraanduidingIdentificatie} |
One adres by its nummeraanduiding’s identificatie, including an ingetrokken one |
/v1/adressen/_dichtstbij |
The adressen of the adresseerbaar object nearest to a point |
Paths have no trailing slash, except the root /v1/, which answers like /v1. Anything else is a 404.
The root says which snapshot the server reads: its peildatum, the extract’s date when known, and whether it is a database with tables or an adreslijst.
{ "titel": "bagkit", "beschrijving": "Adressen uit een momentopname van de Basisregistratie Adressen en Gebouwen (BAG).", "versie": "1.0.0", "dataset": { "indeling": "tabellen", "peildatum": "2026-10-08", "extractdatum": "2026-10-08" }, "links": [ { "rel": "self", "href": "/v1", "type": "application/json" }, { "rel": "service-desc", "href": "/v1/openapi.json", "type": "application/vnd.oai.openapi+json;version=3.0" } ]}Finding adressen
Section titled “Finding adressen”/v1/adressen needs a postcode and returns its current adressen, leaving out ingetrokken nummeraanduidingen, ordered by huisnummer, huisletter, huisnummertoevoeging and identificatie.
| Parameter | Meaning |
|---|---|
postcode |
Required. Four digits, the first not 0, and two letters, without a space: 9902HA |
huisnummer |
A huisnummer from 1 to 99999 |
huisletter |
One letter, matched case-insensitively |
huisnummertoevoeging |
One to four letters or digits, matched case-insensitively |
exacteMatch |
true makes a left-out huisletter or huisnummertoevoeging mean none; needs huisnummer |
crs |
The CRS of geometrie in the response; see Coordinates |
Without exacteMatch, postcode=7311KZ&huisnummer=110 also finds 110A and 110-2. With exacteMatch=true it finds only the adres without a huisletter or huisnummertoevoeging, so a full adres names one result at most.
{ "adressen": [ { "nummeraanduidingIdentificatie": "0003200000137381", "postcode": "9902HA", "huisnummer": 1, "huisletter": null, "huisnummertoevoeging": null, "openbareRuimteNaam": "Abel Eppensstraat", "openbareRuimteIdentificatie": "0003300000116985", "woonplaatsNaam": "Appingedam", "woonplaatsIdentificatie": "3386", "gemeenteNaam": "Eemsdelta", "provincieNaam": "Groningen", "status": "Naamgeving uitgegeven", "indicatieNevenadres": false, "adresseerbaarObject": { "identificatie": "0003010000129210", "type": "Verblijfsobject", "status": "Verblijfsobject in gebruik", "gebruiksdoelen": ["woonfunctie"], "oppervlakte": 118, "geometrie": { "type": "Point", "coordinates": [6.86877201, 53.31933815] }, "panden": [ { "identificatie": "0003100000119616", "oorspronkelijkBouwjaar": 1960, "status": "Pand in gebruik" } ] } } ]}null means the BAG has no value: no huisletter, or no adresseerbaar object for the nummeraanduiding. indicatieNevenadres says whether the adres is a nevenadres of its object rather than its hoofdadres. oppervlakte is in square metres, gebruiksdoelen are in alphabetical order, and geometrie is the object’s punt as a GeoJSON Point. Statuses and gebruiksdoelen are as the BAG records them.
A request for one adres, such as /v1/adressen/0003200000137381, returns it in the same form, without the adressen wrapper. It also finds an ingetrokken nummeraanduiding, whose status is Naamgeving ingetrokken.
Nearest lookup
Section titled “Nearest lookup”/v1/adressen/_dichtstbij returns the current adressen of the adresseerbaar object nearest to a point, hoofdadres first, each with its afstand in metres:
curl 'http://localhost:8080/v1/adressen/_dichtstbij?punt=6.86877,53.31934'| Parameter | Meaning |
|---|---|
punt |
Required. Two numbers separated by a comma: lengtegraad and breedtegraad, or x and y in RD New |
puntCrs |
The CRS of punt; CRS84 when left out |
crs |
The CRS of geometrie in the response |
The search widens from 100 m up to 25.6 km. Ligplaatsen and standplaatsen count as well as verblijfsobjecten, ties go to the smallest identificatie, and a point farther from every adres, or outside the Netherlands, returns an empty list. Looking up addresses explains how the search works.
Coordinates
Section titled “Coordinates”Punten are GeoJSON Points in one of two coordinate reference systems, named by URI as the Geospatial module asks:
| CRS | URI | Coordinates |
|---|---|---|
| WGS84 (CRS84), default | http://www.opengis.net/def/crs/OGC/1.3/CRS84 |
Lengtegraad and breedtegraad, to 8 decimals |
| RD New (EPSG:28992) | http://www.opengis.net/def/crs/EPSG/0/28992 |
x and y in metres, to 3 decimals |
Pass the URI as crs for the response and as puntCrs for the point you search from, percent-encoded or as it is. Responses with geometrie name their CRS in a Content-Crs header:
Content-Crs: <http://www.opengis.net/def/crs/OGC/1.3/CRS84>A database with tables stores punten in RD New; an adreslijst stores WGS84, which /v1/crss reports as storageCrs. Outside RD New’s area of use an object has no WGS84 punt, so its geometrie in CRS84 is null.
From an adreslijst
Section titled “From an adreslijst”An adreslijst stores less than a database with tables, so the API leaves out what it does not have rather than returning null: woonplaatsIdentificatie, status, indicatieNevenadres, and every field of adresseerbaarObject except type and geometrie. /v1 reports the indeling as adreslijst. Nearest lookups need the spatial index of a database with tables and answer 501.
Headers
Section titled “Headers”Every response carries:
| Header | Value |
|---|---|
API-Version |
The full API version, such as 1.0.0 |
Access-Control-Allow-Origin |
*: any web page may read the API |
Access-Control-Expose-Headers |
API-Version, Content-Crs |
Cache-Control |
no-store |
Content-Security-Policy |
default-src 'none'; frame-ancestors 'none' |
Strict-Transport-Security |
max-age=31536000, which browsers honour only over HTTPS |
X-Content-Type-Options, X-Frame-Options, Referrer-Policy |
nosniff, DENY, no-referrer |
An Accept header must allow application/json, for example application/json, application/* or */*; otherwise the answer is 406.
Errors
Section titled “Errors”Errors are problem details with the type application/problem+json, with Dutch titles and details. A 400 lists every rejected parameter at once in invalidParams, each with a code: required, invalid, repeated, unknown (not a parameter of the resource) or conflict.
{ "status": 400, "title": "Ongeldige aanvraag", "detail": "ongeldige parameters: postcode, houseNumber", "invalidParams": [ { "name": "postcode", "code": "invalid", "reason": "moet bestaan uit vier cijfers, waarvan het eerste geen 0 is, en twee letters, zoals 7311KZ" }, { "name": "houseNumber", "code": "unknown", "reason": "is geen parameter van deze resource" } ]}| Status | Title | When |
|---|---|---|
| 400 | Ongeldige aanvraag | A parameter is missing, unknown, repeated or invalid; invalidParams lists each problem |
| 404 | Niet gevonden | The path is not a resource, or no nummeraanduiding has the identification |
| 405 | Methode niet toegestaan | Anything but GET or HEAD; Allow lists both |
| 406 | Niet aanvaardbaar | Accept rules out application/json |
| 500 | Interne serverfout | The query failed; the server logs why |
| 501 | Niet geïmplementeerd | A nearest lookup against an address list, which has no spatial index |
| 503 | Dienst niet beschikbaar | The database workers are not available, for example while the server stops |
Versions
Section titled “Versions”The v1 in the path is the major version. Changes that keep existing clients working, such as a new resource or field, raise the minor or patch version in API-Version and stay under /v1; an incompatible change starts /v2.
| Version | Changes |
|---|---|
| 1.0.1 | The OpenAPI description documents the Access-Control-Allow-Origin header on /openapi.json, which the API already sent. |
| 1.0.0 | The first versioned API, in Dutch. It replaces the unversioned paths such as /9902HA/1 and /53.31934/6.86877, which no longer exist. |
Deviations from the API Design Rules
Section titled “Deviations from the API Design Rules”bagkit follows the rules except where a self-hosted tool cannot:
- Documentation in English. The OpenAPI description is in Dutch, as the rules ask, but this documentation site is in English, which the rules allow for existing documentation.
- No TLS.
bagkit servespeaks plain HTTP. The rules require TLS for every exchange, so put it behind a reverse proxy that terminates TLS before exposing it. For the same reason the description’s server URL is the relative/v1, not anhttpsaddress, which the checker warns about. - Contact information. The description names bagkit’s maintainer and issue tracker as its contact. An organisation that runs bagkit for others should name its own contact.
mise run api:check checks the description with the DON Checker of developer.overheid.nl.
Limits
Section titled “Limits”- A fixed pool of
--workersdatabase connections answers queries, behind a queue of twice that size. When the queue is full, requests wait for room within their connection’s lifetime. - At most
--max-connectionsconnections (256) are accepted; more are closed immediately. - Each connection lives at most
--connection-timeout-secs(30), including reading headers, waiting in the queue, querying, idle keep-alive and writing the response. This can cut off a slow reader mid-response. - Request headers are limited to 64 fields and 16 KiB.
- Keep-alive works within the connection lifetime.
A server keeps reading the database it opened. After replacing it, restart the server.
© 2026 David Stotijn · MIT License.