DOV Haltes

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:

Treinstations hebben in het CHB meestal geen perrons; die hebben wel een UIC-code en de NS-stationscode als vervoerderscode.

Conventies

OnderwerpAfspraak
Basis-URLhttps://luxxi.nl/dovapi — alle endpoints staan onder /api
FormaatJSON (UTF-8). Alleen GET, behalve POST /api/refresh.
AuthenticatieNiet nodig voor het opvragen van data.
CORSOpen (Access-Control-Allow-Origin: *), dus direct bruikbaar vanuit de browser.
CoördinatenWGS84 lat/lon (6 decimalen, ±1 m), plus de originele Rijksdriehoek rdX/rdY.
CodesBij :code mag je de volledige code (NL:Q:12174140) of alleen het nummer (12174140) gebruiken.
Zoekenq zoekt in naam, plaats en straat; elk woord moet voorkomen, hoofdletters en accenten maken niet uit. Namen die met de zoekterm beginnen komen eerst.
Pagineringlimit (standaard 50, max 1000) en offset. Lijsten geven { total, limit, offset, results }.
Ontbrekende waardenVelden 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);
Tip: in de verkenner zie je bij elke zoekactie welke API-call er gedaan wordt. Die kun je kopiëren.

Endpoints

GET/api/stopplaces

Haltes zoeken en filteren. Zonder parameters krijg je alle haltes (gepagineerd).

ParameterOmschrijvingVoorbeeld
qZoektekst in naam, plaats en straatjanskerkhof
townExacte plaatsnaam (hoofdletterongevoelig)Zwolle
typeType halte, zie waardelijstenrailStation
statusStatus van de halteavailable
placePlacecode (groep haltes), zie /placesNL:P:zl
limit, offsetPaginering20, 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" }]
    },
    …
  ]
}

Probeer: /api/stopplaces?q=zwolle&type=railStation

GET/api/stopplaces/:code

Eén halte, inclusief de volledige perron-objecten in quays.

ParameterOmschrijving
:code verplichtStopplacecode (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).

ParameterOmschrijvingVoorbeeld
qZoektekst in naam, plaats en straatzwarte pad
townExacte plaatsnaam's-Gravenhage
modeModaliteit: bus, tram, metro, ferry, railtram
statusStatus van het perronavailable
stopplaceAlleen perrons van deze halteNL:S:12174110
municipalityCBS-gemeentecodeG0344
concessionCode van de concessieverlenerOVGD
dataownerPerrons met een haltecode van deze vervoerderQBUZZ
wheelchairtrue = rolstoeltoegankelijk, false = niettrue
limit, offsetPaginering
GET /api/quays?q=gravenhage&mode=tram&wheelchair=true&limit=1

Probeer: /api/quays?q=gravenhage&mode=tram&wheelchair=true

GET/api/quays/:code

Eén perron met alle details, plus een samenvatting van de bijbehorende halte in stopplace.

ParameterOmschrijving
:code verplichtQuaycode (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).

ParameterOmschrijvingStandaard
lat verplichtBreedtegraad (WGS84)
lon verplichtLengtegraad (WGS84)
radiusStraal in meters (max 10000)500
kindquays of stopplacesquays
modeModaliteit (alleen bij perrons)
statusStatus, bv. available
limit, offsetPaginering50
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.

Probeer: /api/nearby?lat=52.0907&lon=5.1214&radius=300

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.

ParameterOmschrijving
:dataowner verplichtVervoerderscode, bv. ARR, QBUZZ, RET, GVB, HTM, NS (zie /values)
:code verplichtDe 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>.

ParameterOmschrijving
forcetrue = 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)

VeldTypeOmschrijving
idstringCHB-id, bv. NL:CHB:StopPlace:12174110
stopplacecodestringUnieke haltecode, bv. NL:S:12174110
placecodestring|nullPlaats waartoe de halte behoort (/places)
typestringType halte (waardelijst)
namestringPublieke naam
town, streetstring|nullPlaats en straat
statusstringStatus (waardelijst)
uiccodestring|nullInternationale stationscode (treinstations)
lat, lonnumber|nullWGS84-coördinaten
rdX, rdYnumber|nullRijksdriehoekscoördinaten (EPSG:28992)
ownercodestring|nullBeheerder (/dataowners)
facilitiesobjectVoorzieningen op halteniveau
quaycodesstring[]Codes van de perrons van deze halte
userstopcodesobject[]Vervoerderscodes op halteniveau (vooral NS-stations)
quaysobject[]Alleen bij /stopplaces/:code: volledige perrons

Object: perron (quay)

VeldTypeOmschrijving
idstringCHB-id, bv. NL:CHB:Quay:12174140
quaycodestringUnieke perroncode, bv. NL:Q:12174140
stopplacecodestringHalte waartoe het perron behoort
name, town, streetstring|nullNaam en adres
typestringPerrontype (waardelijst)
statusstringStatus (waardelijst)
transportmodesstring[]Modaliteiten die hier stoppen
lat, lon, rdX, rdYnumber|nullLocatie
levelnumber|nullVerdieping (0 = maaiveld)
bearingnumber|nullRijrichting in graden (0 = noord, 90 = oost)
onlygetoutboolean|nullAlleen uitstappen
municipalitycodestring|nullCBS-gemeentecode
ownercodestring|nullWegbeheerder
concessionprovidercodestring|nullConcessieverlener
accessibilityobjectToegankelijkheid, zie hieronder
facilitiesobjectVoorzieningen, zie hieronder
extraobjectroadcode, hectometersign, greenstop, liftedbicyclepath
remarksstring|nullVrije opmerking van de beheerder
userstopcodesobject[]{ dataownercode, userstopcode, validfrom }: codes waaronder vervoerders dit perron kennen

accessibility

VeldTypeOmschrijving
wheelchairAccessboolean|"unknown"Toegankelijk voor rolstoelgebruikers
stepFreeAccessboolean|"unknown"Drempelvrij bereikbaar
visuallyImpairedAccessboolean|"unknown"Geschikt voor visueel beperkten
disabledaccessible, visuallyaccessible"Y"|"N"|"U"Oudere ja/nee/onbekend-velden
quayshapetypestringlangshalte, haltehaven, bermhalte, semi-haltehaven, …
kerbheight, baylength, narrowestpassagewidth, boardingpositionwidth, …numberMaten in meters
markedkerb, guidelines, lift, ramp, tactilegroundsurfaceindicator, …booleanAanwezigheid 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.

VeldWaarden
halte typeonstreetBus, railStation, onstreetTram, combiTramBus, busStation, metroStation, tramStation, ferryPort, combiMetroTram, other
halte statusavailable (actief), expired (vervallen), adapted (aangepast)
perron typeregular, demandresponsive (op afroep), temporary, season, calamity
perron statusavailable, expired, outofuse (buiten gebruik), plan (gepland)
transportmodesbus, tram, metro, ferry, rail
vervoerders (dataownercode)ARR, CXX (Connexxion/Transdev), QBUZZ, EBS, RET, KEOLIS, GVB, HTM, NS, DELIJN, …
Let op: de data bevat ook vervallen en geplande haltes. Filter op 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" }.

CodeBetekenis
400Ongeldige of ontbrekende parameter (bv. lat/lon bij /nearby)
401Ontbrekend of ongeldig token (alleen /refresh)
404Halte, perron, haltecode of endpoint niet gevonden
502Verversen mislukt omdat de bron niet bereikbaar was
503Data 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).