DOV Haltes API
Alle OV-haltes en perrons van Nederland (en grensgebied) uit het Centraal Haltebestand (CHB), als eenvoudige JSON-API. Met locatie, toegankelijkheid, voorzieningen en de haltecodes die vervoerders gebruiken.
Introductie
De data komt van NDOVloket en wordt automatisch ververst zodra er een nieuwe dagelijkse export is. Er zijn twee soorten objecten:
- Halte (stopplace): de halte zoals een reiziger die kent, bv. “Utrecht, Janskerkhof”. Code
NL:S:…. - Perron (quay): een specifieke instapplek van een halte, meestal één per rijrichting. Code
NL:Q:…. Een halte heeft nul of meer perrons.
Treinstations hebben in het CHB meestal geen perrons; die hebben wel een UIC-code en de NS-stationscode als vervoerderscode.
Conventies
| Onderwerp | Afspraak |
|---|---|
| Basis-URL | https://luxxi.nl/dovapi — alle endpoints staan onder /api |
| Formaat | JSON (UTF-8). Alleen GET, behalve POST /api/refresh. |
| Authenticatie | Niet nodig voor het opvragen van data. |
| CORS | Open (Access-Control-Allow-Origin: *), dus direct bruikbaar vanuit de browser. |
| Coördinaten | WGS84 lat/lon (6 decimalen, ±1 m), plus de originele Rijksdriehoek rdX/rdY. |
| Codes | Bij :code mag je de volledige code (NL:Q:12174140) of alleen het nummer (12174140) gebruiken. |
| Zoeken | q zoekt in naam, plaats en straat; elk woord moet voorkomen, hoofdletters en accenten maken niet uit. Namen die met de zoekterm beginnen komen eerst. |
| Paginering | limit (standaard 50, max 1000) en offset. Lijsten geven { total, limit, offset, results }. |
| Ontbrekende waarden | Velden zonder gegevens zijn null (of een leeg object/lege lijst). |
Een lijst-antwoord ziet er altijd zo uit:
{
"total": 137, // totaal aantal resultaten voor deze filters
"limit": 50,
"offset": 0,
"results": [ … ] // maximaal `limit` objecten
}
Snelstart
Perrons binnen 300 m van de Dom in Utrecht:
curl "https://luxxi.nl/dovapi/api/nearby?lat=52.0907&lon=5.1214&radius=300&status=available"
In JavaScript:
const res = await fetch('https://luxxi.nl/dovapi/api/stopplaces?q=utrecht centraal&limit=5');
const { total, results } = await res.json();
for (const halte of results) console.log(halte.name, halte.town, halte.lat, halte.lon);
Endpoints
GET/api/stopplaces
Haltes zoeken en filteren. Zonder parameters krijg je alle haltes (gepagineerd).
| Parameter | Omschrijving | Voorbeeld |
|---|---|---|
q | Zoektekst in naam, plaats en straat | janskerkhof |
town | Exacte plaatsnaam (hoofdletterongevoelig) | Zwolle |
type | Type halte, zie waardelijsten | railStation |
status | Status van de halte | available |
place | Placecode (groep haltes), zie /places | NL:P:zl |
limit, offset | Paginering | 20, 40 |
GET /api/stopplaces?q=zwolle&type=railStation
{
"total": 2, "limit": 50, "offset": 0,
"results": [
{
"id": "NL:CHB:StopPlace:zl",
"stopplacecode": "NL:S:zl",
"placecode": "NL:P:zl",
"type": "railStation",
"name": "Zwolle",
"town": "Zwolle",
"street": "Stationsplein",
"status": "available",
"uiccode": "8400747",
"rdX": 202775, "rdY": 502178,
"lat": 52.505204, "lon": 6.090857,
"ownercode": "PRORAIL",
"facilities": { "passengerinformationdisplay": false },
"quaycodes": [],
"userstopcodes": [{ "dataownercode": "NS", "userstopcode": "zl", "validfrom": "2022-12-14 10:19:10" }]
},
…
]
}
GET/api/stopplaces/:code
Eén halte, inclusief de volledige perron-objecten in quays.
| Parameter | Omschrijving |
|---|---|
:code verplicht | Stopplacecode (NL:S:12174110 of 12174110) of CHB-id |
GET /api/stopplaces/12174110
{
"stopplacecode": "NL:S:12174110",
"name": "P+R Rolde",
"town": "Rolde",
"type": "onstreetBus",
"status": "available",
"lat": 52.975, "lon": 6.652,
"quaycodes": ["NL:Q:12174140", "NL:Q:12174130", …],
"quays": [ { "quaycode": "NL:Q:12174140", … }, … ],
…
}
Geeft 404 als de halte niet bestaat.
Probeer: /api/stopplaces/12174110
GET/api/quays
Perrons zoeken en filteren. Filters worden gecombineerd (EN).
| Parameter | Omschrijving | Voorbeeld |
|---|---|---|
q | Zoektekst in naam, plaats en straat | zwarte pad |
town | Exacte plaatsnaam | 's-Gravenhage |
mode | Modaliteit: bus, tram, metro, ferry, rail | tram |
status | Status van het perron | available |
stopplace | Alleen perrons van deze halte | NL:S:12174110 |
municipality | CBS-gemeentecode | G0344 |
concession | Code van de concessieverlener | OVGD |
dataowner | Perrons met een haltecode van deze vervoerder | QBUZZ |
wheelchair | true = rolstoeltoegankelijk, false = niet | true |
limit, offset | Paginering |
GET /api/quays?q=gravenhage&mode=tram&wheelchair=true&limit=1
GET/api/quays/:code
Eén perron met alle details, plus een samenvatting van de bijbehorende halte in stopplace.
| Parameter | Omschrijving |
|---|---|
:code verplicht | Quaycode (NL:Q:12174140 of 12174140) of CHB-id |
GET /api/quays/12174140
{
"id": "NL:CHB:Quay:12174140",
"quaycode": "NL:Q:12174140",
"stopplacecode": "NL:S:12174110",
"name": "P+R Rolde",
"town": "Rolde",
"street": "Grolloerstraat",
"type": "regular",
"status": "available",
"transportmodes": ["bus"],
"rdX": 239974, "rdY": 554973,
"lat": 52.975053, "lon": 6.652251,
"level": 0,
"bearing": 330,
"onlygetout": false,
"municipalitycode": "G1680",
"ownercode": "P0022",
"concessionprovidercode": "OVGD",
"accessibility": {
"disabledaccessible": "N",
"stepFreeAccess": true,
"wheelchairAccess": false,
"visuallyaccessible": "N",
"visuallyImpairedAccess": false,
"quayshapetype": "haltehaven",
"kerbheight": 0.18,
"ramp": true,
…
},
"facilities": {
"shelter": true, "seatavailable": true, "illuminatedstop": true,
"passengerinformationdisplay": true, "passengerinformationdisplaytype": "VMD",
…
},
"extra": { "roadcode": "n376", "hectometersign": 7.7, "greenstop": false, "liftedbicyclepath": false },
"remarks": null,
"userstopcodes": [{ "dataownercode": "QBUZZ", "userstopcode": "12174140", "validfrom": "…" }],
"stopplace": { "stopplacecode": "NL:S:12174110", "name": "P+R Rolde", … }
}
Probeer: /api/quays/12174140
GET/api/nearby
Perrons of haltes rond een punt, gesorteerd op afstand. Elk resultaat heeft een extra veld distance (meters).
| Parameter | Omschrijving | Standaard |
|---|---|---|
lat verplicht | Breedtegraad (WGS84) | |
lon verplicht | Lengtegraad (WGS84) | |
radius | Straal in meters (max 10000) | 500 |
kind | quays of stopplaces | quays |
mode | Modaliteit (alleen bij perrons) | |
status | Status, bv. available | |
limit, offset | Paginering | 50 |
GET /api/nearby?lat=52.0907&lon=5.1214&radius=300&status=available
{
"total": 4, "limit": 50, "offset": 0,
"results": [
{ "distance": 95, "name": "Domplein", "quaycode": "NL:Q:50006750", "transportmodes": ["bus"], … },
{ "distance": 203, "name": "Janskerkhof", "quaycode": "NL:Q:50006610", "transportmodes": ["bus"], … },
…
]
}
Geeft 400 als lat of lon ontbreekt.
GET/api/userstopcodes/:dataowner/:code
Zoek het perron bij de haltecode die een vervoerder intern gebruikt, bijvoorbeeld in realtime-data (KV6/KV17) of dienstregelingen (KV1/NeTEx). Geeft altijd een lijst, omdat een code soms bij meerdere perrons hoort.
| Parameter | Omschrijving |
|---|---|
:dataowner verplicht | Vervoerderscode, bv. ARR, QBUZZ, RET, GVB, HTM, NS (zie /values) |
:code verplicht | De haltecode van die vervoerder. Hoofdletters maken niet uit (Swk90 = swk90). |
Voorbeeld: NS gebruikt codes als Swk90, Zzs91 (stationsafkorting + volgnummer) voor bushaltes bij stations. Zoek die direct op, zonder de suffix eraf te halen: /api/userstopcodes/NS/Swk90.
GET /api/userstopcodes/ARR/10380100
[ { "quaycode": "NL:Q:10380110", "name": "…", "userstopcodes": [ … ], … } ]
Geeft 404 als de combinatie niet bestaat.
Probeer: /api/userstopcodes/ARR/10380100
GET/api/values
Alle waarden die in de huidige data voorkomen, met aantallen. Handig voor dropdowns en om filters te valideren.
{
"stopplaceTypes": [{ "value": "onstreetBus", "count": 26196 }, …],
"stopplaceStatuses": [{ "value": "available", "count": 25151 }, …],
"quayTypes": [{ "value": "regular", "count": 49248 }, …],
"quayStatuses": [{ "value": "available", "count": 44458 }, …],
"transportmodes": [{ "value": "bus", "count": 51113 }, …],
"userstopDataowners": [{ "value": "ARR", "count": 20750 }, …],
"concessionproviders": [{ "value": "OVGD", "count": 1234, "name": "…" }, …]
}
Probeer: /api/values
GET/api/places
Plaatsen: groepen van haltes die bij elkaar horen (bv. alle haltes van station Zwolle). Gebruik de placecode als filter place op /stopplaces.
{ "total": 708, …, "results": [{ "id": "NL:CHB:GroupOfStopPlaces:zl", "placecode": "NL:P:zl", "name": "Zwolle", "town": "Zwolle" }, …] }
Probeer: /api/places
GET/api/dataowners
Beheerders van haltes: gemeenten, provincies, vervoersregio's en andere wegbeheerders. De code komt terug in ownercode en concessionprovidercode.
{ "total": 424, …, "results": [{ "code": "G0197", "name": "Aalten", "type": "ROOW" }, …] }
type: ROOW = wegbeheerder, PUCO = concessieverlener, COPR = vervoerder, INT = intern/overig.
Probeer: /api/dataowners
GET/api/status
Welke export er geladen is, wanneer die is opgehaald en hoeveel objecten erin zitten.
{
"ready": true,
"syncing": false,
"lastError": null,
"refreshHours": 1,
"exportDate": "2026-09-25",
"syncedAt": "2026-09-25T08:15:55.858Z",
"files": { "chb": "ExportCHB_2026-09-25.xml.gz", "assignments": "PassengerStopAssignmentExportCHB_2026-09-25.xml.gz" },
"counts": { "stopplaces": 27899, "quays": 53088, "places": 708, "dataowners": 424, "userstopcodes": 75551 }
}
Probeer: /api/status
POST/api/refresh
Beheerders-endpoint: haalt direct de nieuwste export op in plaats van te wachten op de automatische check. Vereist de header Authorization: Bearer <token>.
| Parameter | Omschrijving |
|---|---|
force | true = ook opnieuw verwerken als de export niet nieuwer is |
curl -X POST -H "Authorization: Bearer $TOKEN" "https://luxxi.nl/dovapi/api/refresh"
{ "updated": false, "exportDate": "2026-09-25", … }
Een verversing duurt 1–2 minuten; de API blijft in die tijd gewoon de vorige data serveren.
Object: halte (stopplace)
| Veld | Type | Omschrijving |
|---|---|---|
id | string | CHB-id, bv. NL:CHB:StopPlace:12174110 |
stopplacecode | string | Unieke haltecode, bv. NL:S:12174110 |
placecode | string|null | Plaats waartoe de halte behoort (/places) |
type | string | Type halte (waardelijst) |
name | string | Publieke naam |
town, street | string|null | Plaats en straat |
status | string | Status (waardelijst) |
uiccode | string|null | Internationale stationscode (treinstations) |
lat, lon | number|null | WGS84-coördinaten |
rdX, rdY | number|null | Rijksdriehoekscoördinaten (EPSG:28992) |
ownercode | string|null | Beheerder (/dataowners) |
facilities | object | Voorzieningen op halteniveau |
quaycodes | string[] | Codes van de perrons van deze halte |
userstopcodes | object[] | Vervoerderscodes op halteniveau (vooral NS-stations) |
quays | object[] | Alleen bij /stopplaces/:code: volledige perrons |
Object: perron (quay)
| Veld | Type | Omschrijving |
|---|---|---|
id | string | CHB-id, bv. NL:CHB:Quay:12174140 |
quaycode | string | Unieke perroncode, bv. NL:Q:12174140 |
stopplacecode | string | Halte waartoe het perron behoort |
name, town, street | string|null | Naam en adres |
type | string | Perrontype (waardelijst) |
status | string | Status (waardelijst) |
transportmodes | string[] | Modaliteiten die hier stoppen |
lat, lon, rdX, rdY | number|null | Locatie |
level | number|null | Verdieping (0 = maaiveld) |
bearing | number|null | Rijrichting in graden (0 = noord, 90 = oost) |
onlygetout | boolean|null | Alleen uitstappen |
municipalitycode | string|null | CBS-gemeentecode |
ownercode | string|null | Wegbeheerder |
concessionprovidercode | string|null | Concessieverlener |
accessibility | object | Toegankelijkheid, zie hieronder |
facilities | object | Voorzieningen, zie hieronder |
extra | object | roadcode, hectometersign, greenstop, liftedbicyclepath |
remarks | string|null | Vrije opmerking van de beheerder |
userstopcodes | object[] | { dataownercode, userstopcode, validfrom }: codes waaronder vervoerders dit perron kennen |
accessibility
| Veld | Type | Omschrijving |
|---|---|---|
wheelchairAccess | boolean|"unknown" | Toegankelijk voor rolstoelgebruikers |
stepFreeAccess | boolean|"unknown" | Drempelvrij bereikbaar |
visuallyImpairedAccess | boolean|"unknown" | Geschikt voor visueel beperkten |
disabledaccessible, visuallyaccessible | "Y"|"N"|"U" | Oudere ja/nee/onbekend-velden |
quayshapetype | string | langshalte, haltehaven, bermhalte, semi-haltehaven, … |
kerbheight, baylength, narrowestpassagewidth, boardingpositionwidth, … | number | Maten in meters |
markedkerb, guidelines, lift, ramp, tactilegroundsurfaceindicator, … | boolean | Aanwezigheid van voorzieningen |
facilities
Booleans, tenzij anders vermeld: shelter (abri), seatavailable, leantosupport, illuminatedstop, stopsign, stopsigntype (string), timetableinformation, routenetworkmap, passengerinformationdisplay, passengerinformationdisplaytype (string), audiobutton, infounit, bicycleparking, numberofbicycleplaces (number), bins, ovccico (OV-chip in/uitchecken), ovccharging (OV-chip opladen), shelterpublicity.
Waardelijsten
Actuele lijsten met aantallen via /api/values.
| Veld | Waarden |
|---|---|
halte type | onstreetBus, railStation, onstreetTram, combiTramBus, busStation, metroStation, tramStation, ferryPort, combiMetroTram, other |
halte status | available (actief), expired (vervallen), adapted (aangepast) |
perron type | regular, demandresponsive (op afroep), temporary, season, calamity |
perron status | available, expired, outofuse (buiten gebruik), plan (gepland) |
transportmodes | bus, tram, metro, ferry, rail |
vervoerders (dataownercode) | ARR, CXX (Connexxion/Transdev), QBUZZ, EBS, RET, KEOLIS, GVB, HTM, NS, DELIJN, … |
status=available als je alleen haltes wilt die nu in gebruik zijn. Den Haag heet in de data 's-Gravenhage.Foutcodes
Fouten hebben altijd de vorm { "error": "omschrijving" }.
| Code | Betekenis |
|---|---|
400 | Ongeldige of ontbrekende parameter (bv. lat/lon bij /nearby) |
401 | Ontbrekend of ongeldig token (alleen /refresh) |
404 | Halte, perron, haltecode of endpoint niet gevonden |
502 | Verversen mislukt omdat de bron niet bereikbaar was |
503 | Data wordt nog geladen (alleen direct na een herstart); probeer het na een minuut opnieuw |
Bron & actualiteit
Bron: het Centraal Haltebestand, gepubliceerd door NDOVloket op data.ndovloket.nl/haltes (bestanden ExportCHB_*.xml.gz en PassengerStopAssignmentExportCHB_*.xml.gz). De bron wordt dagelijks bijgewerkt; deze API controleert elk uur of er een nieuwe export is. Welke export geladen is, zie je in /api/status.
Coördinaten zijn omgerekend van Rijksdriehoek naar WGS84 met een benaderingsformule (nauwkeurigheid circa 1 meter).