# Public transport data via the NDOV desk

The NDOV desk collects timetables and live trip information from Dutch transport operators, from Arriva and Connexxion to GVB, RET and NS. Stichting OpenGeo runs the desk under the name GOVI and publishes the data under CC0: timetables in NeTEx, the stop register and live positions and delays. There is also a ready-made GTFS version for the whole country.

- Publisher: GOVI (NDOV-loket, Stichting OpenGeo)
- Area: Netherlands · Finest detail: stop, trip and vehicle
- Period: heden (archief op aanvraag) · Refreshed: every minute
- Access: Open · Licence: CC0 1.0
- Join keys: stop_id (halte), trip_id (rit)
- Status: ok (2026-10-16) · GTFS-Realtime voertuigposities (597 kB)
- URL: https://govi.nu

## What can you do with it?
- **How far is the nearest stop?.** Put the GTFS stops on a map with addresses or neighbourhoods. Count per neighbourhood how many stops lie within 400 metres and how often something departs per hour.
- **Live map of all buses.** The vehicle positions file is refreshed continuously. On 15 October 2026 around 10.30 pm it held 1,658 vehicles.
- **Which lines are late most often?.** Store the trip updates for a few weeks and compare expected with scheduled times. For longer periods Stichting OpenGeo supplies historical vehicle data on request, over ten years according to its FAQ.
- **What disappears from the timetable?.** Compare the current NeTEx or GTFS timetable with that of a year earlier. Count per municipality the trips that are gone.

## How to process it
1. **Start with GTFS.** openOV converts the NDOV data to GTFS for the whole country. The zip is large: 251 MB on 14 October 2026. Ask for Last-Modified with a HEAD request first and only download when something changed.
   `curl -I https://gtfs.ovapi.nl/nl/gtfs-nl.zip`
2. **Live positions and delays.** The same folder has GTFS-Realtime files in protobuf: vehiclePositions.pb (positions), tripUpdates.pb (expected times), alerts.pb and trainUpdates.pb.
   `https://gtfs.ovapi.nl/nl/vehiclePositions.pb`
3. **Source data in NeTEx and the stop register.** data.ndovloket.nl is an open folder. Under netex/ there is a daily NeTEx file per operator, under haltes/ the central stop register (CHB) as gzipped XML.
   `https://data.ndovloket.nl/`
4. **Real-time from the source via ZeroMQ.** If you want the raw streams (KV6 positions, KV17 trip changes, NS InfoPlus, SIRI), subscribe via ZeroMQ to an envelope such as /ARR/KV6posinfo. Best effort, at most one connection per user per stream.
   `tcp://pubsub.besteffort.ndovloket.nl:7658`
5. **Be frugal.** openOV asks you to identify yourself in the User-Agent, use If-Modified-Since and accept gzip. Timetables rarely change more than once a day; the new service day starts at 03:00 UTC.

## Interesting combinations
- Do lower-income neighbourhoods have worse public transport? → [cbs-kwb](https://www.opendataindex.nl/en/bron/cbs-kwb.md) via coördinaten van de halte (punt in buurt). Work out the neighbourhood of each stop, count departures per neighbourhood and compare with income and car ownership from the key figures.
- Is the bus stuck in the same jam as the car? → [ndw-verkeer](https://www.opendataindex.nl/en/bron/ndw-verkeer.md) via tijdstip + locatie. Compare delays of bus trips on a route with measured speed on the road alongside.

## Similar sources
- [ns-reisinformatie](https://www.opendataindex.nl/en/bron/ns-reisinformatie.md): Trains only, through NS's own API, with a key. Includes disruptions, crowding and rolling stock.
- [OVapi en openOV](https://gtfs.ovapi.nl): The GTFS and GTFS-Realtime files on this page come from here, built from NDOV data by Bliksem Labs.

## What to watch out for
- **Best effort, no guarantee.** The open streams have no service agreement. For a guaranteed connection, sign a user agreement with GOVI. There is no technical support; questions go to the OpenOV Discord.
- **Not every operator delivers everything.** According to GOVI the large operators deliver planned and live data, but for FlixBus, the Westerschelde Ferry and German operators, for example, there is only a timetable. Check the overview on the datasets page.
- **No history in the open files.** The real-time files show only the present. To measure punctuality, store them yourself or request historical data.
- **Don't pose as an operator.** The openOV licence allows everything except claiming to represent one of the operators. Report data errors at ovdata.nl.

## API (GTFS / GTFS-RT (protobuf), NeTEx-bestanden en REST (JSON))

- Base: https://gtfs.ovapi.nl/nl
- Access: No key or account for the files, GTFS-RT and v0.ovapi.nl. A recognisable User-Agent is required (openOV usage rule). A guaranteed real-time connection needs a signed NDOV user agreement. (tested)
- Formats: gtfs (zip), protobuf, netex (xml.gz), json
- Size: 1439 vehicles in vehiclePositions.pb, from 8 operators (16 Oct 2026, 23:36) (tested)
- Filtering: No query language: the feeds are whole files for the whole Netherlands. Filter yourself on agency, route or stop_id. With v0.ovapi.nl you pick a stop, line or journey in the path.; 

### Endpoints
- `GET /gtfs-nl.zip`: Timetable of the whole Netherlands in GTFS (241 MB; daily, last 16 Oct 2026 18:58 UTC). Contains stops.txt (7 MB), routes.txt, trips.txt (75 MB), stop_times.txt (1.17 GB unzipped), shapes.txt (289 MB), calendar_dates.txt, transfers.txt and feed_info.txt, among others.
- `GET /vehiclePositions.pb`: GTFS-Realtime: position of every vehicle in service with trip, stop and delay (192 kB, new every minute).
- `GET /tripUpdates.pb`: GTFS-Realtime: expected arrival and departure times per trip and stop (1.9 MB).
- `GET /trainUpdates.pb`: GTFS-Realtime for trains, with scheduled and actual platform (1.3 MB).
- `GET /alerts.pb`: GTFS-Realtime: disruptions and notices (461 kB).
- `GET /archive/`: Older GTFS files by date (NL-YYYYMMDD.gtfs.zip, 170–210 MB each, from 1 January 2026).

### Parameters
- `If-None-Match / If-Modified-Since`: openOV usage rule: only fetch again when something changed. Unchanged gives 304 with no body (tested on gtfs-nl.zip). (tested)
- `Accept-Encoding`: gzip; required by openOV's usage rules, also useful with v0.ovapi.nl. (from the description)
- `User-Agent`: Say who you are, with contact details; that lets openOV work without registration. (from the description)

### Fields in GTFS-RT, GTFS and v0.ovapi.nl (selection) (9)
`entity.id` tekst, `vehicle.trip.trip_id / route_id / start_date` tekst, `vehicle.position.latitude / longitude` getal, `vehicle.stop_id / current_stop_sequence / current_status` tekst, `vehicle.timestamp` integer, `ovapi_vehicle_position.delay` integer, `ExpectedDepartureTime / TargetDepartureTime` datetime, `TripStopStatus` tekst, `stop_id / stop_name / stop_lat / stop_lon` tekst

### Limits and pitfalls
- No documented rate limit, but rules: recognisable User-Agent, If-Modified-Since/If-None-Match, gzip. Timetables rarely change more than daily; the new service day starts at 03:00 UTC. (from the description)
- ZeroMQ: at most one connection per user per stream, best effort, no service level. (from the description)
- The v0.ovapi.nl wiki asks to bundle several objects in one call (commas) and not to scrape the same stops at a short interval; heavy users should run KV78Turbo themselves. (from the description)
- GTFS-RT is protobuf, not JSON. Read it with gtfs-realtime-bindings (Python, Node) and OVapi's .proto file for the extra fields; without a library a simple varint reader also works. (tested)
- No trains in vehiclePositions: on 16 Oct 2026 23:36 it contained only CXX, ARR, QBUZZ, GVB, EBS, RET, HTM and KEOLIS. Trains are in trainUpdates.pb (without positions). (tested)
- The GTFS file is large (241 MB, stop_times 1.17 GB unzipped). Ask for Last-Modified or ETag with HEAD first. The root folder also has govi (partial, with KV8) and openov-nl (without AVV and Thalys). (tested)
- Stop coordinates in /stopareacode/ of v0.ovapi.nl are not always right: Papendrecht, Pieter Zeemanlaan was at 47.97 N 3.31 E (in France). Take coordinates from the CHB or the GTFS stops. (tested)
- The v0.ovapi.nl wiki still mentions the old GOVI licence (keep no longer than 30 minutes); the NDOV data itself is now CC0. When in doubt, keep the source files from data.ndovloket.nl. (from the description)
- No robots.txt on gtfs.ovapi.nl, data.ndovloket.nl and v0.ovapi.nl (404). (tested)

### Example
```
http://v0.ovapi.nl/tpc/30005071
```

## How to fetch it

### curl
```bash
curl -sI https://gtfs.ovapi.nl/nl/vehiclePositions.pb
```

### python
```python
import requests
from google.transit import gtfs_realtime_pb2   # pip install gtfs-realtime-bindings

r = requests.get("https://gtfs.ovapi.nl/nl/vehiclePositions.pb",
                 headers={"User-Agent": "mijn-project (mail@voorbeeld.nl)"})
feed = gtfs_realtime_pb2.FeedMessage()
feed.ParseFromString(r.content)
print(len(feed.entity), "voertuigen")
v = feed.entity[0].vehicle
print(v.trip.trip_id, v.position.latitude, v.position.longitude)
```

## What you can build with it
- [Train delay and disruption dashboard](https://www.opendataindex.nl/en/bouwen/treinvertragingen-dashboard.md): A page that shows every minute how many trains are running, which ones are late and which disruptions are going on, built on the open GTFS-Realtime file from openOV and, with a free key, the NS disruptions.
- [Live map of buses, trams and metros](https://www.opendataindex.nl/en/bouwen/ov-live-kaart.md): A map on which every bus, tram and metro in the Netherlands moves live, coloured by delay, from one open 180 kB file and without a key.


## Examples with real figures (1)

For each example the finding, the reference date and the call that fetches exactly these figures.

- **Buses, trams and metros with a live position, by operator: most Qbuzz (39 vehicles), then Connexxion and EBS** (oktober 2026). Table vehiclePositions.pb. [Example](https://www.opendataindex.nl/en/voorbeeld/ndov-voertuigen-vervoerder)
  - `https://gtfs.ovapi.nl/nl/vehiclePositions.pb`
