# DUO open education data

Every school and programme with its address, and per school location the number of pupils, school advice, end-of-primary test scores, exam results and forecasts. From primary school to university. DUO publishes it as downloads and as an API (56 datasets with 274 tables in the datastore on 16 October 2026).

- Publisher: Dienst Uitvoering Onderwijs
- Area: Netherlands · Finest detail: school location
- Period: 2010 – heden · Refreshed: daily
- Access: Open · Licence: CC BY (volgens de catalogus van DUO)
- Join keys: instellingscode (BRIN), vestigingscode, gemeentenummer, postcode
- Status: ok (2026-10-16) · 56 datasets · nieuwste wijziging: RIO nfo, po, vo, vavo, mbo en ho
- URL: https://duo.nl/open_onderwijsdata/

## What can you do with it?
- **What advice do children get at your primary school?.** Count the school advice per location per year (since 2010) and compare the school with others in the municipality.
- **Shrinking and growing schools.** Pupils per location are available from 2010 to 2025. Find schools that lost half their pupils in ten years.
- **School advice next to the neighbourhood.** Link locations by postcode or municipality number to income and education from the CBS key figures. Do children in poorer areas get lower advice?
- **Where do the pupils come from?.** The postcode4 file shows per location which postcode areas the pupils come from. That tells you how far parents travel for a school.

## How to process it
1. **Find the dataset in the catalogue.** The catalogue is a CKAN portal. Search for a word and check which files (resources) belong to each dataset.
   `https://onderwijsdata.duo.nl/api/3/action/package_search?q=advies&rows=5`
2. **Query rows through the datastore.** Each CSV file is also in the datastore. With resource_id, filters and sort you fetch only what you need. This example: school advice for one school in 2024.
   `https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=454c5dd4-6815-4b52-ae2f-b9eebc6afa6d&filters=%7B%22INSTELLINGSCODE%22%3A%2200AP%22%2C%22PEILJAAR%22%3A2024%7D`
3. **Or download the whole file.** For a full analysis the CSV is easier. Some download links in the catalogue point to an internal server (beheer-ggm-ckan-prd…); replace the host with onderwijsdata.duo.nl.
   `https://onderwijsdata.duo.nl/dataset/d6903313-8c64-4b12-b4ac-07e5655ef9f4/resource/454c5dd4-6815-4b52-ae2f-b9eebc6afa6d/download/brin6_advies.csv`
4. **Join to addresses.** Pupil files only have the institution and location code. Name, address, postcode and municipality number are in the address files (for example all primary school locations).
   `https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=dcc9c9a5-6d01-410b-967f-810557588ba4&limit=5`
5. **Remove the small numbers.** A -1 means 1 to 4 pupils (suppressed). Don't count it as a number; use a lower and upper bound or leave it out.
6. **Accredited programmes in higher education.** The Overzicht Erkenningen ho (CC BY) gives per institution, location and programme the recognition, accreditation dates, study load, degree and status, current and historical: 74,173 rows on 16 October 2026. This dataset is only in DUO's catalogue, not on data.overheid.nl.
   `https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=28a4d89b-c223-4dbc-8deb-9d02a533f215&limit=5`

## Interesting combinations
- Is school advice related to income in the municipality? → [cbs-kwb](https://www.opendataindex.nl/en/bron/cbs-kwb.md) via gemeentenummer (GM). Put GM before the municipality number from the address file (1699 becomes GM1699) and join to the key figures of the same year.
- What kind of buildings are the schools in? → [pdok-bag](https://www.opendataindex.nl/en/bron/pdok-bag.md) via postcode + huisnummer. Look up each location's address in the BAG for construction year and floor area.

## Similar sources
- [cbs-statline](https://www.opendataindex.nl/en/bron/cbs-statline.md): CBS has education figures per municipality and nationally, not per school.
- [Scholen op de kaart](https://scholenopdekaart.nl/): School profiles for parents, partly based on DUO data. A website, not a dataset.

## What to watch out for
- **Counts below 5 are suppressed.** The API then shows -1, some files show '<5'. For small schools that is a large share of the cells.
- **Not every dataset is current.** The register of schools and programmes (RIO) is refreshed daily, pupil counts once a year (reference date 1 October). Exam results per location in the API run to school year 2017 (as of 15 October 2026).
- **Codes without explanation in the API.** The ADVIES field is a number (for example 5, 7 or 11), not text. Look up the meaning at DUO before you count.
- **No SQL on the datastore.** datastore_search_sql returns 'no access'. Grouping and counting is up to you, after fetching.
- **Not everything is also on data.overheid.nl.** A few datasets are only in the DUO catalogue, such as Overzicht Erkenningen ho and Sectorale Ontwikkelpaden (which programmes belong to a development path and whether SLIM subsidy is possible). Sectorale Ontwikkelpaden has no licence in the catalogue (16 October 2026): licence not established.

## API (CKAN (Action API v3 + datastore))

- Base: https://onderwijsdata.duo.nl/api/3/action
- Access: No key or account needed (tested)
- Formats: json, csv
- Size: 274 tables in the datastore (of 290 files in 56 datasets) (tested)
- Filtering: No query language: equality filters as JSON in filters (value or list of values), plus full-text search with q. No greater-than or less-than; datastore_search_sql is disabled.; = (filters), IN (lijst in filters), q (vrij zoeken)

### Endpoints
- `GET /package_list`: Names of all 56 datasets (e.g. adressen_bo, 03_voex-v1, historische-leerlingenaantallen-po).
- `GET /package_search?q=…&fq=…&rows=…`: Search the catalogue; rows=0 returns the count only. Also returns each dataset's resources (files) with id, format and datastore_active.
- `GET /package_show?id={naam}`: One dataset: description, licence, metadata_modified and the resources with their resource_id and download URL.
- `GET /resource_show?id={resource_id}`: One file: name, format, last_modified, download URL.
- `GET /group_list`: The eight topic groups (basisonderwijs, voorgezet-onderwijs, hoger-onderwijs, inburgering …).
- `GET /group_show?id={groep}`: One group with title and description.
- `GET /datastore_search?resource_id={id}`: The rows of one table, with filters, full-text search, sorting, column choice and paging. The response also lists the columns with their type.
- `GET /dataset/{dataset-id}/resource/{resource_id}/download/{bestand}.csv`: The whole file as CSV (path without /api/3/action, on https://onderwijsdata.duo.nl).

### Parameters
- `resource_id`: Required for datastore_search: the file id (from package_show). (tested)
- `filters`: Equality filter per column as JSON, also with a list: {"PEILJAAR":[2023,2024]}. Column names exactly as in fields (sometimes with spaces: "BEVOEGD GEZAG NUMMER"). (tested)
- `q`: Full-text search in all columns, or per column: {"PLAATSNAAM":"utrecht"} (word match, case-insensitive). (tested)
- `fields`: Only these columns, comma-separated. (tested)
- `sort`: Sorting, e.g. PEILJAAR desc. (tested)
- `limit`: Number of rows; default 100 (CKAN). 40,000 was served fine; limit=0 returns only total and columns. (tested)
- `offset`: Paging; the response has _links.next with the next page. (tested)
- `records_format`: objects (default), lists or csv (rows as one CSV string, no header). (tested)
- `include_total`: false skips counting (faster on large tables). (tested)
- `q / fq / rows / start / sort (package_search)`: Solr search in the catalogue: fq=groups:basisonderwijs (24 datasets), sort=metadata_modified desc. (tested)

### Fields in the datastore (selection: school advice and primary school addresses) (8)
`INSTELLINGSCODE` text, `VESTIGINGSCODE` text, `PEILJAAR` numeric, `ADVIES` numeric, `AANTAL_LEERLINGEN` numeric, `GEMEENTENUMMER` text, `POSTCODE` text, `_id` int

### Limits and pitfalls
- No key and no documented rate limit. Default 100 rows per call; a limit of 40,000 was served. Page with offset or _links.next. (tested)
- Only some CKAN actions are open (package_list, package_search, package_show, resource_show, group_list, group_show, datastore_search). (tested)
- Other CKAN actions (status_show, organization_list, tag_list, datastore_info, help_show) return HTTP 200 with the website's HTML, not a JSON error. Always check that the response is JSON. datastore_search_sql returns 400 "Action name not known". (tested)
- distinct=true counts correctly (total 15 years in the school advice table) but returned only one row. For unique values, prefer a small column selection with a high limit. (tested)
- Privacy: counts of 1 to 4 appear as -1 in the API (as <5 in some files). Don't add them up as numbers. (tested)
- Nine download URLs in the catalogue point to an internal host (beheer-ggm-ckan-prd.apps.prd.duo.rijksapps.nl) that doesn't resolve outside DUO; replace the host with onderwijsdata.duo.nl. (tested)
- Pupil files only have BRIN and branch code; name, address and municipality come from the address files (adressen_bo, adressen_vo …). Municipality number has no GM prefix: add GM and leading zeros to join with CBS. (experience)
- Resource ids don't change with a new delivery; the dataset's metadata_modified and the resource's last_modified give freshness (addresses are refreshed daily). (experience)

### Example
```
https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=454c5dd4-6815-4b52-ae2f-b9eebc6afa6d&filters=%7B%22PEILJAAR%22%3A2024%7D&sort=AANTAL_LEERLINGEN%20desc&limit=3
```

## How to fetch it

### curl
```bash
curl -G "https://onderwijsdata.duo.nl/api/3/action/datastore_search" --data-urlencode "resource_id=454c5dd4-6815-4b52-ae2f-b9eebc6afa6d" --data-urlencode "filters={\"PEILJAAR\":2024}" --data-urlencode "limit=5"
```

### python
```python
import requests

r = requests.get("https://onderwijsdata.duo.nl/api/3/action/datastore_search", params={
    "resource_id": "454c5dd4-6815-4b52-ae2f-b9eebc6afa6d",  # schooladviezen per vestiging
    "sort": "PEILJAAR desc", "limit": 5})
for rij in r.json()["result"]["records"]:
    print(rij["INSTELLINGSCODE"], rij["VESTIGINGSCODE"], rij["PEILJAAR"], rij["ADVIES"], rij["AANTAL_LEERLINGEN"])
```

## What you can build with it
- [School finder: primary schools around your address, with pupil numbers and school advice](https://www.opendataindex.nl/en/bouwen/schoolkeuzehulp.md): Type a postcode and house number and see every primary school within a radius, sorted by distance, with their pupil numbers over the years and what share of the children got a havo or vwo advice. Only open data from DUO and the BAG.


## What is in it: 348 datasets and tables

All datasets in DUO's catalogue, by subject as DUO groups them, with every file, fetched every night. Every table has its own page with the number of rows, the columns and the API call.

- Kinds: tabel 276, dataset 56, bestand 16
- Last changed: 2026-11-01 (154 in the past 30 days)
- With an API call on the dataset page: 348
- Search: https://www.opendataindex.nl/api/catalogus/duo?q=… or the MCP function zoek_datasets with herkomst=duo
- Every row has its own page: https://www.opendataindex.nl/en/dataset/duo/<key>

### Folders
- Arbeidsovereenkomst en cao (5): Functiemix (5)
- Basisonderwijs (74): Adressen van hoofdvestigingen, vestigingen en besturen in het basisonderwijs (4), Adressen van instellingen, vestigingen en besturen in het speciaal onderwijs (4), Gemiddelde eindscores per vestiging bo en sbo (2), Heroverwegingen en bijgestelde adviezen bo en sbo (2), Historische leerlingenaantallen PO (3), Kwaliteitsoordelen bevoegde gezagen, po, so en vo (3), Leerlingen bo en sbo per vestiging naar advies (2), Leerlingen bo en sbo per vestiging naar leerjaar (2) …
- Hoger onderwijs (38): Adressen in het hoger onderwijs (3), Eerstejaars ingeschrevenen hoger onderwijs in het domein hoger onderwijs (7), Gediplomeerden in het hoger onderwijs in het domein soort diploma soort hoger onderwijs (7), HO Opleidingsoverzicht (3), Ingeschrevenen hoger onderwijs in het domein hoger onderwijs (7), Inschrijvingen in het hoger onderwijs (7), Overzicht Erkenningen ho (4)
- Inburgering (3): WI 2021 Instroom, Uitstroom en Leerroute (3)
- Middelbaar beroepsonderwijs (43): Adressen mbo (3), MBO Opleidingsaanbod (4), Studentprognoses mbo per instelling (36)
- Speciaal onderwijs en leerproblemen (55): Aantal leerlingen per cluster per schoolvestiging (2), Adressen van instellingen, vestigingen en besturen in het speciaal onderwijs (4), Gemiddelde eindscores per vestiging bo en sbo (2), Heroverwegingen en bijgestelde adviezen bo en sbo (2), Historische leerlingenaantallen PO (3), Kwaliteitsoordelen bevoegde gezagen, po, so en vo (3), Leerlingen bo en sbo per vestiging naar advies (2), Leerlingen bo en sbo per vestiging naar leerjaar (2) …
- Voortgezet onderwijs (33): Aantal afgenomen examens en gemiddelde examencijfers per vak per vestiging (2), Aantal leerlingen per cluster per schoolvestiging (2), Adressen van instellingen, vestigingen en besturen in het voortgezet onderwijs (4), Kwaliteitsoordelen bevoegde gezagen, po, so en vo (3), Leerlingen po per gemeente (5), Leerlingen po per samenwerkingsverband (5), Leerlingen SO en VSO (4), Prognoses vo per instelling (2) …
- Zonder groep (165): Changelog van HO Opleidingsoverzicht (2), Changelog van MBO Opleidingsaanbod (2), Changelog van Overzicht Erkenningen HO (2), Changelog van RIO nfo po vo vavo mbo ho (11), Changelog van Sectorale Ontwikkelspaden (2), Gediplomeerde mbo-studenten (2), Het toegestane voortgezet onderwijs per schoolvestiging en leerjaar. (2), Instromende mbo-studenten (11) …

## Examples with real figures (2)

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

- **Pupils in primary education: 1.36 million in 2025/26, 10% less than in 1996/97** (2025/26). Table 947ecf4a-3744-4f59-ad98-6ce4107dafa2. [Example](https://www.opendataindex.nl/en/voorbeeld/duo-leerlingen-basisonderwijs)
  - `https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=947ecf4a-3744-4f59-ad98-6ce4107dafa2&filters=%7B%22SOORT_VESTIGING%22%3A%22BO%22%7D&fields=SCHOOLJAAR%2CAANTAL_LEERLINGEN&limit=32000`
- **Vocational (MBO) students by field: most Health and social care (141,737 students), then Business and administration and Engineering and process industry** (oktober 2025). Table da6669b9-5894-4ba6-9ba4-bfd42f8d3387. [Example](https://www.opendataindex.nl/en/voorbeeld/duo-mbo-studenten-hoofdgroep)
  - `https://onderwijsdata.duo.nl/api/3/action/datastore_search?resource_id=da6669b9-5894-4ba6-9ba4-bfd42f8d3387&limit=10000`
