303,525 datasets indexed1,419 publishers
Open Data IndexThe index of open data about the Netherlands
Open Data Index›Build›National budget tracker: budgeted against spent
DashboardAdvancedFinanceGovernment

National budget tracker: budgeted against spent

Track per ministry and per budget article what central government planned to spend and what it actually spent, year after year, with the motions and amendments the House filed on it.

Your AI assistantOpen Data Index MCP on
The Open Data Index MCP server is on in your chat, so the assistant reads this project and its sources itself.
You paste this project's prompt
The assistant fetches from Open Data Index
  • geef_project("rijksbegroting-tracker")
  • geef_bron("rijksfinancien")
  • geef_bron("tweedekamer-opendata")
The assistant

I have the project, the tested calls and the pitfalls. I'll build a dashboard with Central government budget (Rijksfinanciën open data) and Dutch House of Representatives open data. This is how it could look:

You build this yourself. Copy the prompt into Claude, ChatGPT or Cursor, with the MCP server on, or follow the steps below.

Follow the steps
begroting-ocw.rkw — Rekenwerksaved
Sans · 10BIU,00Format if
D9fx=C9-B9
ABCDE
1Chapter VIII · Education, Culture and Science
2Year (€ bn)BudgetSpentDiff.Over
3201636.8538.70+1.84
4201737.1638.05+0.89
5201838.8742.33+3.46
6201941.9843.08+1.09
7202042.7244.46+1.74
8202143.6048.75+5.15
9202247.6453.20+5.56
10202353.2555.12+1.88
11202455.4756.46+1.00
12202557.6059.53+1.94
13
14Article 2025BudgetSpentDiff.%
151Primary education16.2916.90+0.61+3.8%
163Secondary education11.8212.33+0.51+4.3%
177University education7.077.41+0.33+4.7%
184Vocational and adult education5.966.14+0.18+3.0%
196Higher professional education4.494.65+0.16+3.5%
2016Research and science policy1.711.83+0.12+6.8%
2111Student finance6.606.57−0.03−0.4%
229Labour market and staff policy0.410.37−0.04−10.1%
23House, 36800 VIII✓✗OtherTotal
24Motions393922100
25Amendments310922
+VIII OCWAll chaptersMotions
Source: rijksfinancien.nl · House of RepresentativesSum: 5.56
Mockup

This is a mockup, not a working version, with the years and motions from our test run on 16 October 2026 and the 2025 articles from a call on 17 October 2026, 23:32 (OCW budget tables, budget and annual report). Turn the MCP server on with the address on For AI.

Who it's for

Journalists and fact-checkers who want to know whether a ministry spent more or less than budgeted, MPs and their staff, public administration students and anyone who wants to see where tax money goes.

What you get

A dashboard per ministry (budget chapter) with, per year, the budget next to the annual report, the difference per article, and below that the motions and amendments the House filed on that budget, with their outcome.

Test run on 16 October 2026 for Education, Culture and Science (chapter VIII): in every year from 2016 to 2025 the ministry spent more than budgeted. In 2025, 57.60 billion euros were budgeted and 59.53 billion spent (1.94 billion more). The largest gap was in 2022: 47.64 billion budgeted, 53.20 billion spent. Primary education went from 16.29 to 16.90 billion in 2025. On the 2026 education budget (paper 36800 VIII) MPs filed 100 motions: 39 adopted, 39 rejected, 6 taken over by the minister, 6 lapsed. Of the 22 amendments, 3 were adopted and 10 rejected; 9 have no decision, usually because they were replaced by an amended version.

Spending by the Ministry of Education by budget article: most primary education (17,733 million euros), then secondary education and universities

Spending by the Ministry of Education by budget article, 2027, in million euros. Total: 60,774; the largest 12 of 15 categories are shown. Primary education is 29% of the total. Chapter VIII (Onderwijs Cultuur en Wetenschap), draft budget 2027, expenditure (vuo=U). Amounts in the API are in thousands of euros.

Source: Ministerie van Financiën, Rijksfinanciën open data (budgettaire tabellen) · opgehaald 25 oktober 2026 · selection: hoofdstuk VIII; fase OWB; vuo=U (uitgaven)Open the table Single block

How you could start6 steps

  1. 1

    Fetch the budget of one ministry

    The Rijksfinanciën budget tables have one row per scheme. year and phase are required; phase=OWB is the budget, vuo=U the expenditure, chapter the budget chapter (VIII is Education). The amount is in thousands of euros: 14552052 is 14.6 billion. Education had 265 rows for 2025.

    https://www.rijksfinancien.nl/open-data/api/json/budgettaire_tabellen?year=2025&phase=OWB&vuo=U&chapter=VIII
    [{"index":"VIII.1.70.1.596032.U","jaar":2025,"fase":"OWB","verantwoordelijk_minister":null,
      "hoofdstuk_naam":"Onderwijs, Cultuur en Wetenschap","hoofdstuk_nummer":"VIII",
      "artikel_naam":"Primair onderwijs","artikel_nummer":"1", …
      "regeling_detailniveau_naam":"Bekostiging po-instellingen","regeling_detailniveau_nummer":"596032",
      "vuo":"U","bedrag":14552052}, …]
  2. 2

    Put the annual report next to it, and join on name

    With phase=JV you get what was actually spent. Watch out: the same scheme has a different number in the budget and the annual report of the same year (Bekostiging po-instellingen: 596032 in the OWB, 686087 in the JV). So join on article number, or on article plus scheme name, never on regeling_detailniveau_nummer. There is no annual report yet for 2026 and 2027.

    https://www.rijksfinancien.nl/open-data/api/json/budgettaire_tabellen?year=2025&phase=JV&vuo=U&chapter=VIII
    [{"index":"VIII.1.70.1.686087.U","jaar":2025,"fase":"JV","verantwoordelijk_minister":null,
      "hoofdstuk_naam":"Onderwijs, Cultuur en Wetenschap","hoofdstuk_nummer":"VIII",
      "artikel_naam":"Primair onderwijs","artikel_nummer":"1", …
      "regeling_detailniveau_naam":"Bekostiging po-instellingen","regeling_detailniveau_nummer":"686087",
      "vuo":"U","bedrag":15254960}, …]
  3. 3

    Check the fase field yourself

    The specification also lists O1 and O2 (the supplementary budgets), but on 16 October 2026 phase=O1 for 2025 returned 564 rows: the 265 from the OWB and the 299 from the JV mixed together. For 2026, O1 simply returned the OWB. So always filter on the fase field in the rows, not only on the parameter.

    https://www.rijksfinancien.nl/open-data/api/json/budgettaire_tabellen?year=2025&phase=O1&vuo=U&chapter=VIII
    [{"index":"VIII.1.70.1.596032.U","jaar":2025,"fase":"OWB", … "bedrag":14552052},
     …
     {"index":"VIII.1.70.1.686087.U","jaar":2025,"fase":"JV", … "bedrag":15254960}, …]
  4. 4

    Find the budget file in the House of Representatives

    Every budget is a parliamentary file (Kamerstukdossier) with a number and the chapter as suffix (VIII). The title names the year. Use the House's new OData v5; Verwijderd is a field you always filter on.

    https://gegevensmagazijn.tweedekamer.nl/OData/v5/Kamerstukdossier?$filter=startswith(Titel,'Vaststelling van de begrotingsstaten') and Toevoeging eq 'VIII' and Verwijderd eq false&$orderby=Nummer desc&$top=3&$select=Nummer,Toevoeging,Titel
    {"@odata.context":"…/OData/v5/$metadata#Kamerstukdossier(Nummer,Toevoeging,Titel)","value":[
      {"Titel":"Vaststelling van de begrotingsstaten van het Ministerie van Onderwijs, Cultuur en Wetenschap (VIII) voor het jaar 2027","Nummer":37020,"Toevoeging":"VIII"},
      {"Titel":"… voor het jaar 2026","Nummer":36800,"Toevoeging":"VIII"},
      {"Titel":"… voor het jaar 2025","Nummer":36600,"Toevoeging":"VIII"}]}
  5. 5

    Fetch the motions and amendments with their outcome

    Cases (Soort Motie or Amendement) hang off the file. With $expand=Besluit you see the outcome in Besluit.Soort ('Stemmen - aangenomen', 'Stemmen - verworpen', 'Overgenomen' …). Decisions do not come back in order; pick the strongest outcome yourself. $count=true gives the total: 100 motions on the 2026 education budget. At most 250 per request.

    https://gegevensmagazijn.tweedekamer.nl/OData/v5/Zaak?$filter=Soort eq 'Motie' and Verwijderd eq false and Kamerstukdossier/any(k:k/Nummer eq 36800 and k/Toevoeging eq 'VIII')&$count=true&$top=1&$select=Nummer,Onderwerp&$expand=Besluit($select=Soort;$filter=Verwijderd eq false)
    {"@odata.context":"…/OData/v5/$metadata#Zaak(Nummer,Onderwerp,Besluit(Soort))","@odata.count":100,"value":[
      {"Nummer":"2026Z00817","Onderwerp":"Motie van het lid Van der Plas over de verankering van agrarisch erfgoed, visserijcultuur en streekgebonden tradities in het toekomstige cultuurbestel ",
       "Besluit":[{"Soort":"Ingediend"},{"Soort":"Stemmen - verworpen"}]}]}
  6. 6

    Optional: who receives the money?

    The financial instruments name the recipients of grants and contributions, up to 2025. Note three differences: the year parameter is called jaar here (not year), amounts are whole euros (not thousands) and you always get 25 results per page (continue with start).

    https://www.rijksfinancien.nl/open-data/api/json/v2/financiele_instrumenten/ontvangers?jaar=2025&sort=amount desc
    {"jaar":2025,"search":null,"max":41063185000,"min":-3934539000,"total":234370967000,"total_results":21193,
     "records_show":25,"records_start":0,"result":[
      {"id":"zorginstituut nederland","titel":"Zorginstituut Nederland","bedrag":41063185000},
      {"id":"sociale verzekeringsbank","titel":"Sociale Verzekeringsbank","bedrag":29107897000},
      {"id":"tennet holding b.v.","titel":"TenneT Holding B.V.","bedrag":11900000000}, …]}

How the sources fit together

Let AI build it

Paste this prompt into Claude, ChatGPT, Cursor or Lovable. It names the real endpoints, the join keys and the pitfalls, so the assistant writes working code straight away. Always check the result against the source itself.

Build a small dashboard (Next.js or a single HTML file with JavaScript) where I pick a ministry and see what central government planned to spend and what it actually spent, per year and per budget article, with the motions and amendments of the House of Representatives on that budget below.

Source 1, Rijksfinanciën (no key, OpenAPI at https://www.rijksfinancien.nl/swagger.json):
https://www.rijksfinancien.nl/open-data/api/json/budgettaire_tabellen?year=2025&phase=OWB&vuo=U&chapter=VIII
- year and phase are required. phase=OWB is the budget, phase=JV the annual report. vuo=U expenditure, V commitments, O revenue. chapter is the chapter as a Roman numeral (VIII = Education, X = Defence, XVI = Health).
- Fields: jaar, fase, hoofdstuk_nummer, hoofdstuk_naam, artikel_nummer, artikel_naam, artikelonderdeel_naam, regeling_detailniveau_naam, regeling_detailniveau_nummer, vuo, bedrag.
- bedrag is in THOUSANDS of euros (57,596,669 = 57.6 billion). In the financial instruments (recipients) it is whole euros.
- Always filter on the fase field yourself: phase=O1 or O2 returns OWB and JV rows mixed (measured 16 October 2026).
- Join budget and annual report on artikel_nummer (or article plus scheme name). regeling_detailniveau_nummer differs between OWB and JV of the same year and changes every year.
- Do not add up all chapters: funds (Municipalities Fund, Mobility Fund) get their money from ministries, which double counts.
- Health (XVI): the Wlz and Zvw articles (12, 4111, 4112) contain the premium-funded money in the OWB (tens of billions), not in the JV. Leave them out or show them separately.
- Annual reports exist up to 2025; OWB up to 2027. Not every chapter has a JV every year: show "no annual report" instead of 0.

Source 2, House of Representatives OData v5 (no key): https://gegevensmagazijn.tweedekamer.nl/OData/v5
- File: /Kamerstukdossier?$filter=startswith(Titel,'Vaststelling van de begrotingsstaten') and Toevoeging eq 'VIII' and Verwijderd eq false. The title ends with "voor het jaar 2026".
- Motions: /Zaak?$filter=Soort eq 'Motie' and Verwijderd eq false and Kamerstukdossier/any(k:k/Nummer eq 36800 and k/Toevoeging eq 'VIII')&$expand=Besluit($select=Soort;$filter=Verwijderd eq false). Same with Soort eq 'Amendement'.
- The outcome is in Besluit.Soort: 'Stemmen - aangenomen', 'Stemmen - verworpen', 'Overgenomen', 'Stemmen - aangehouden', 'Termijn - vervallen …', 'Ingediend'. Decisions are not in chronological order: take the strongest outcome. Amendments without a decision were usually replaced by an amended version.
- $top at most 250; follow @odata.nextLink. Use v5 field names (Datum, not GestartOp).

Output: a ministry picker; a bar chart per year (2016 to 2025) with budgeted next to spent in billions; a table per article for the chosen year with the difference, largest first; below that the motions and amendments on the following year's budget, with outcome and the case number (for example 2026Z00817). Put the unit and the phase next to every number.

Try it yourself

The core of the project in a small script. Needs: Node 20 of nieuwer (ingebouwde fetch); draai met: node rijksbegroting.mjs [hoofdstuk]. Tested on 2026-10-16; below is the real output of that run.

// Rijksbegroting-tracker: begroot (OWB) tegen uitgegeven (JV) per jaar en per artikel, plus de moties op de begroting.
// Bronnen: rijksfinancien.nl open data (geen sleutel) en de Tweede Kamer OData v5 (geen sleutel). Node 20+.
const UA = { "User-Agent": "mijn-project/1.0 (+jij@voorbeeld.nl)" };
const HOOFDSTUK = process.argv[2] ?? "VIII";      // VIII = Onderwijs, Cultuur en Wetenschap
const JAREN = [2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025];
// VWS (XVI): Wlz en Zvw (artikel 12, 4111, 4112) staan in de OWB met de premiegelden, in het JV niet. Laat ze weg.
const PREMIE = HOOFDSTUK === "XVI" ? new Set(["12", "4111", "4112"]) : new Set();

async function tabel(jaar, fase) {
  const q = new URLSearchParams({ year: jaar, phase: fase, vuo: "U", chapter: HOOFDSTUK });
  const r = await fetch(`https://www.rijksfinancien.nl/open-data/api/json/budgettaire_tabellen?${q}`, { headers: UA });
  const rijen = await r.json();
  // Let op: controleer het veld fase zelf. Bij phase=O1/O2 geeft de API OWB- en JV-regels door elkaar terug.
  return rijen.filter((x) => x.fase === fase && x.jaar === jaar && !PREMIE.has(x.artikel_nummer));
}
const som = (rijen) => rijen.reduce((s, x) => s + x.bedrag, 0);              // bedrag = duizenden euro's
const mld = (k) => (k / 1e6).toFixed(2).replace(".", ",").padStart(6);       // duizenden -> miljarden

console.log(`Hoofdstuk ${HOOFDSTUK}: begroot (OWB) tegen uitgegeven (JV), uitgaven in miljard euro`);
console.log(" jaar  begroot  uitgegeven  verschil");
let laatste;
for (const jaar of JAREN) {
  const [owb, jv] = await Promise.all([tabel(jaar, "OWB"), tabel(jaar, "JV")]);
  if (!jv.length) { console.log(` ${jaar}  ${mld(som(owb))}     (nog geen jaarverslag)`); continue; }
  const v = som(jv) - som(owb);
  console.log(` ${jaar}  ${mld(som(owb))}  ${mld(som(jv))}      ${v >= 0 ? "+" : "-"}${mld(Math.abs(v)).trim()}`);
  laatste = { jaar, owb, jv };
}

// Per artikel in het nieuwste jaar met een jaarverslag. Koppel op artikelnummer, NIET op regelingnummer:
// regeling_detailniveau_nummer verschilt zelfs tussen OWB en JV van hetzelfde jaar.
const perArtikel = {};
for (const [k, rijen] of [["owb", laatste.owb], ["jv", laatste.jv]])
  for (const x of rijen) {
    const a = (perArtikel[x.artikel_nummer] ??= { naam: x.artikel_naam, owb: 0, jv: 0 });
    a[k] += x.bedrag;
  }
console.log(`\n${laatste.jaar} per artikel (miljard euro), grootste verschil eerst:`);
for (const [nr, a] of Object.entries(perArtikel).sort((p, q) => Math.abs(q[1].jv - q[1].owb) - Math.abs(p[1].jv - p[1].owb)).slice(0, 6))
  console.log(`  ${nr.padStart(2)} ${a.naam.slice(0, 34).padEnd(34)} ${mld(a.owb)} -> ${mld(a.jv)}`);

// De Kamer: moties en amendementen bij de begroting van dit hoofdstuk voor 2026.
const TK = "https://gegevensmagazijn.tweedekamer.nl/OData/v5";
const tk = async (pad, q) => (await fetch(`${TK}/${pad}?${new URLSearchParams(q)}`, { headers: UA })).json();
const dossier = (await tk("Kamerstukdossier", {
  $filter: `startswith(Titel,'Vaststelling van de begrotingsstaten') and Toevoeging eq '${HOOFDSTUK}' and contains(Titel,'jaar 2026') and Verwijderd eq false`,
  $select: "Nummer,Toevoeging,Titel",
})).value[0];
console.log(`\nKamerstuk ${dossier.Nummer} ${dossier.Toevoeging}: ${dossier.Titel}`);
for (const soort of ["Motie", "Amendement"]) {
  const zaken = [];
  let url = `${TK}/Zaak?` + new URLSearchParams({
    $filter: `Soort eq '${soort}' and Verwijderd eq false and Kamerstukdossier/any(k: k/Nummer eq ${dossier.Nummer} and k/Toevoeging eq '${HOOFDSTUK}')`,
    $select: "Nummer,Onderwerp", $expand: "Besluit($select=Soort;$filter=Verwijderd eq false)", $top: "250",
  });
  while (url) { const j = await (await fetch(url, { headers: UA })).json(); zaken.push(...j.value); url = j["@odata.nextLink"]; }
  // Besluit.Soort: 'Stemmen - aangenomen', 'Stemmen - verworpen', 'Overgenomen', 'Stemmen - aangehouden', 'Ingediend' …
  // De volgorde van de besluiten in het antwoord is niet chronologisch: kies de sterkste uitkomst.
  const UITKOMST = ["aangenomen", "verworpen", "ingetrokken", "overgenomen", "vervallen", "aangehouden", "ingediend"];
  const uitslag = (z) => UITKOMST.find((u) => z.Besluit.some((b) => b.Soort.toLowerCase().includes(u))) ?? "geen besluit";  // vaak vervangen door een gewijzigde versie
  const telling = {};
  for (const z of zaken) { const u = uitslag(z); telling[u] = (telling[u] ?? 0) + 1; }
  console.log(`  ${soort === "Motie" ? "moties" : "amendementen"}: ${zaken.length} (` + Object.entries(telling).map(([k, n]) => `${k} ${n}`).join(", ") + ")");
}
Output
Hoofdstuk VIII: begroot (OWB) tegen uitgegeven (JV), uitgaven in miljard euro
 jaar  begroot  uitgegeven  verschil
 2016   36,85   38,70      +1,84
 2017   37,16   38,05      +0,89
 2018   38,87   42,33      +3,46
 2019   41,98   43,08      +1,09
 2020   42,72   44,46      +1,74
 2021   43,60   48,75      +5,15
 2022   47,64   53,20      +5,56
 2023   53,25   55,12      +1,88
 2024   55,47   56,46      +1,00
 2025   57,60   59,53      +1,94

2025 per artikel (miljard euro), grootste verschil eerst:
   1 Primair onderwijs                   16,29 ->  16,90
   3 Voortgezet onderwijs                11,82 ->  12,33
   7 Wetenschappelijk onderwijs           7,07 ->   7,41
   4 Beroepsonderwijs en volwassenenedu   5,96 ->   6,14
   6 Hoger beroepsonderwijs               4,49 ->   4,65
  16 Onderzoek en wetenschapsbeleid       1,71 ->   1,83

Kamerstuk 36800 VIII: Vaststelling van de begrotingsstaten van het Ministerie van Onderwijs, Cultuur en Wetenschap (VIII) voor het jaar 2026
  moties: 100 (verworpen 39, aangenomen 39, overgenomen 6, vervallen 6, aangehouden 2, ingetrokken 1, ingediend 7)
  amendementen: 22 (geen besluit 9, verworpen 10, aangenomen 3)

Where it goes wrong

  • Amounts in the budget tables are thousands of euros, in the financial instruments whole euros. The specification does not say so. Divide by 1,000,000 for billions in the tables, by 1,000,000,000 for the recipients.
  • phase=O1 and O2 do not work as described: you get OWB and JV rows mixed (2025) or simply the OWB (2026). Always filter on the fase field in the rows.
  • Scheme numbers are not a stable key: Bekostiging po-instellingen is 596032 in the 2025 budget and 686087 in the 2025 annual report. Join on article number or on name.
  • Health cannot be compared without cleaning: the 2025 OWB has 66.8 billion on article 4111 (Health Insurance Act), the annual report 9.7 billion. The premium money is only in the budget. Before 2021 the Health articles were also organised differently.
  • Do not add all chapters into 'the national budget': funds get their money from ministries and chapter IX (Finance and National Debt) includes repayment of the national debt.
  • In the House API the decisions on one motion do not come back in order ('Stemmen - verworpen' can come before 'Ingediend'). Choose the outcome by content, not position. Amendments without a decision were usually replaced by an amended version.

Take it further

  • Put the 2027 draft budget (phase=OWB, year=2027) next to 2026 and show per article what goes up or down: exactly what journalists look for on Budget Day.
  • Link the recipients (financial instruments) to the article the money comes from (/v2/financiele_instrumenten with referrer and type) and make a top 10 of recipients per article.
  • Follow adopted amendments: fetch the votes per party ($expand=Besluit($expand=Stemming)) and show which parties moved money.
  • Compare the Municipalities Fund (chapter B) with the sum of the general grant in municipal budgets (Iv3, task field 0.7): what central government sends against what municipalities book.

More with this data

Amendments filed: 982 per year in 2025, 7% more than in 2009

Amendments filed, 2009 – 2025, per year. The highest point was 2016 (1,163).

Source: Tweede Kamer, Gegevensmagazijn (Zaak, alleen volle jaren) · opgehaald 25 oktober 2026Open the table Single block

The sources2

Central government budget (Rijksfinanciën open data)

Ministerie van FinanciënNetherlands, budget scheme

The Ministry of Finance publishes the budget tables of all ministries and funds through an open API: expenditure, commitments and revenue per chapter, article and scheme, for the budget, the supplementary budgets and the annual report. Plus who received which grant or contribution (financial instruments, 2016–2025). The draft budget for 2027 was already there on 16 October 2026; the Ministry of Education alone accounts for 60.8 billion euros.

OpenAPIDownloadFinanceGovernment
Reachable
quarterly

Dutch House of Representatives open data

Tweede KamerNetherlands, motion, vote, member

Everything the House does, as an open API: motions and amendments, how each party voted, written questions, debates and committees, parliamentary papers, and the members themselves, with their side jobs, gifts and trips.

OpenAPIGovernment
Up to date
updated yesterday

More to build