303,525 datasets indexed1,419 publishers
Open Data IndexThe index of open data about the Netherlands
Open Data Index›Build›Healthcare cost comparer per municipality
ToolGetting startedHealthFinance

Healthcare cost comparer per municipality

A tool that shows what basic health insurance spends per resident in your municipality, per type of care, and how that compares with the rest of the Netherlands, also after correcting for age.

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("zorgkosten-vergelijker")
  • geef_bron("vektis-zorgkosten")
  • geef_bron("cbs-kwb")
  • geef_bron("waarstaatjegemeente")
The assistant

I have the project, the tested calls and the pitfalls. I'll build a tool with Vektis open data on healthcare costs, Key figures for districts and neighbourhoods and Waarstaatjegemeente.nl (municipal benchmark). 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
zsh — zorg+
~/zorg $ zorgkosten utrecht --year 2024
✓ vektis 2024 · 12,992 rows · 2.9 MB
✓ 342 of 342 municipalities matched to CBS

UTRECHT GM0344 · 378,121 res.
cost per insured year      € 2,898
rank                       333 of 342 (low cost)
age/sex adjusted           index 102 · rank 74

CARE TYPE         €/YR    LESS MORE     
hospital care    1,395  -22%
pharmacy           278  -23%
mental health      374  +61%
nursing            117  -37%
aids                80  -31%
GP other            96   +1%
  difference vs all municipalities

~/zorg $ zorgkosten top 3
HIGHEST           €/YR  IDX  LOWEST       €/YR  IDX
Heerlen          4,520  125  Urk         2,601   96
Landgraaf        4,520  120  Staphorst   2,641   86
Brunssum         4,471  119  Rozendaal   2,700   78
  Netherlands € 3,349 per insured year = 100
~/zorg $ 
Mockup

This is a mockup, not a working version, with the figures from our test run on 16 October 2026 with the Vektis file for cost year 2024 (published 20 July 2026). Turn the MCP server on with the address on For AI.

Who it's for

Councillors and policy officers in health and social care, health journalists, public health students, and anyone who wants to know whether 'our municipality is expensive' is really true or just because many older people live there.

What you get

A comparer where you pick a municipality and see: healthcare costs per insured year, its rank among all 342 municipalities, an index after correcting for age and sex (Netherlands = 100) and, per type of care, the difference with all municipalities together.

Test run on 16 October 2026 with the newest file (cost year 2024, published 20 July 2026): - Basic health insurance paid out 59,525 million euros in 2024 for 17,773,377 insured years: 3,349 euros per insured year. - All 342 municipality names matched to a CBS code (2026 boundaries), with two fixed aliases. - Highest costs: Heerlen and Landgraaf (both 4,520 euros per insured year). Lowest: Urk (2,601), Staphorst (2,641) and Rozendaal (2,700). - After correcting for age and sex, Heerlen stays the most expensive (index 125), but Urk comes out at 96: its low costs are mostly because 34% of its insured years are under 20 (nationally 20%). Lowest index: Hilvarenbeek (77) and Rozendaal (78). - Utrecht: 2,898 euros per insured year, rank 333 of 342, so one of the cheapest. After correction Utrecht ranks 74th with index 102: slightly more expensive than you would expect with such a young population. Mental health consultations cost 374 euros per insured year in Utrecht, 61% more than in all municipalities together (233).

Basic health insurance costs per insured person: highest in Heerlen and 1 other municipality (4,520 euros), lowest in Urk (2,601 euros)

Basic health insurance costs per insured person per municipality, 2024, in euros per insured year. Netherlands as a whole: 3,349. 342 municipalities with a figure.

Source: Vektis, Open Databestand Zorgverzekeringswet 2024 (gemeente); bron: Vektis · opgehaald 25 oktober 2026 · selection: kostenjaar 2024; basisverzekering (Zvw), alle kostenrubrieken; per verzekerdejaarOpen the table Single block

How you could start6 steps

  1. 1

    Find the newest file

    Vektis has no API but fixed file names per cost year. Try the path with descending years using a HEAD request: on 16 October 2026, 2025 returned 404 and 2024 returned 200 (2,864,955 bytes, Last-Modified 20 July 2026). According to Vektis the next file arrives mid-2027. Watch the exceptions: 2020 is called ..._versie2.csv and 2011 to 2016 sit in a different folder.

  2. 2

    Read the CSV and set the residual category aside

    Semicolon separator, dot as decimal mark, 12,992 data rows: 342 municipalities × 2 sexes × 19 age bands, plus one residual category. That first data row has no sex, age or municipality (412,394 insured people, 220,748.91 insured years): groups smaller than ten and unknown characteristics. Count it in the national total, not in any municipality. There are 26 columns starting with kosten_; the names are truncated at 32 characters (kosten_medisch_specialistische_z).

    https://www.vektis.nl/vektisnl-assets/prd/storage/uploads/Docs per pagina/Open Data Bestanden/2024/Vektis Open Databestand Zorgverzekeringswet 2024 - gemeente.csv
    geslacht;leeftijdsklasse;gemeentenaam;aantal_bsn;aantal_verzekerdejaren;kosten_medisch_specialistische_z;kosten_farmacie;kosten_huisarts_inschrijftarief;...
    ;;;412394;220748.91;75738481.61;11107600.71;6081467.62;2120101.39;1852201.82;7886723.95;...
    M;0 t/m  4 jaar;AA EN HUNZE;503;462.82;594197.18;32444.9;35500.43;20536.52;15357.5;60736.99;...
    M;0 t/m  4 jaar;AALSMEER;731;665.75;1829500.58;55985.58;51112.38;25609.76;17045.38;96918.18;...
  3. 3

    Match the municipality names to CBS codes

    The Vektis file only holds the name in capitals ('S GRAVENHAGE', 'NOARDEAST-FRYSLAN', 'BERGEN LB'), no GM code. The 2024 file follows the 2026 municipal boundaries, so take the municipality list from the Key figures for districts and neighbourhoods 2026 (86358NED). Normalise both names (strip accents, punctuation and spaces, upper case). CBS distinguishes equal names with a province ('Bergen (L.)', 'Bergen (NH.)', 'Beek (L.)'): use the name without brackets only when it is unique. Two aliases remain: BERGEN LB = Bergen (L.) and NUENEN CA = Nuenen, Gerwen en Nederwetten. With those, all 342 match.

    https://datasets.cbs.nl/odata/v1/CBS/86358NED/WijkenEnBuurtenCodes?$filter=DimensionGroupId eq 'GM'&$select=Identifier,Title
    {"@odata.context":"https://datasets.cbs.nl/odata/v1/CBS/86358NED/$metadata#WijkenEnBuurtenCodes(Identifier,Title)","value":[
     {"Identifier":"GM1680","Title":"Aa en Hunze"},{"Identifier":"GM0358","Title":"Aalsmeer"},
     {"Identifier":"GM0197","Title":"Aalten"},{"Identifier":"GM0059","Title":"Achtkarspelen"},
     {"Identifier":"GM0482","Title":"Alblasserdam"},{"Identifier":"GM0613","Title":"Albrandswaard"}, ...
  4. 4

    Fetch residents per municipality (for the map and as a check)

    Residents on 1 January 2026 is Measure T001036. Use it for the tooltip and as a check, not as the denominator: divide costs by aantal_verzekerdejaren from the Vektis file itself. An insured year reflects how long someone was insured and lived in the municipality (anyone who moves, is born or dies counts partly). Nationally: 17,773,377 insured years in 2024 against 18,130,208 residents on 1 January 2026. We use datasets.cbs.nl (OData v4): codes without padding, CORS open. The old API at opendata.cbs.nl returned 503 on 16 October 2026 around 23:30.

    https://datasets.cbs.nl/odata/v1/CBS/86358NED/Observations?$filter=startswith(WijkenEnBuurten,'GM') and Measure eq 'T001036'&$select=WijkenEnBuurten,Value
    {"@odata.context":"https://datasets.cbs.nl/odata/v1/CBS/86358NED/$metadata#Observations(WijkenEnBuurten,Value)","value":[
     {"Value":26062.0,"WijkenEnBuurten":"GM1680"},{"Value":33459.0,"WijkenEnBuurten":"GM0358"},
     {"Value":27608.0,"WijkenEnBuurten":"GM0197"},{"Value":28254.0,"WijkenEnBuurten":"GM0059"}, ...
  5. 5

    Calculate per insured year and correct for age and sex

    Per municipality, add up all kosten_ columns and divide by insured years. Then compare fairly with indirect standardisation: nationally (without the residual category) compute costs per insured year for every combination of sex and age band (38 cells), multiply by the municipality's insured years in those cells and add up. That is what the municipality 'should' cost. Actual divided by expected times 100 is the index. An 85-year-old costs far more than a 25-year-old: without correction you mostly see where older people live. Don't sort age bands as text: '0 t/m 4 jaar' and '5 t/m 9 jaar' have a double space and the last band is '90+'.

  6. 6

    Waarstaatjegemeente: look, don't automate

    The Zorggebruik dashboard on waarstaatjegemeente.nl (VNG) shows comparable municipal figures already processed. The OData API behind it is in beta and only for municipalities (API key, at most 40 municipalities, documentation in a closed Pleio group). We have no key, so we did not test the API. robots.txt disallows /handlers/ and /zoeken and asks for 2 seconds between requests: don't automate the Viewer. What is open: the RSS feed with recent changes, useful to see when new figures arrive. If you work for a municipality, request a key via VNG Realisatie.

    https://www.waarstaatjegemeente.nl/feed.ashx
    <?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="style/rss.xsl"?><rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0"><channel>
    <title>Waarstaatjegemeente.nl - cijfers en statistieken van gemeenten</title>
    <item><category>Link</category><title>Monitor Brede Regeling Combinatiefuncties 2025</title><description />
     <pubDate>Fri, 16 Oct 2026 00:00:00 +0200</pubDate><datum>16-10-2026</datum></item>
    <item><category>News</category><title>Waarstaatjegemeente.nl in VNG Magazine: deze maand aandacht voor de Wet open overheid</title> ...

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 healthcare cost comparer per municipality: a small Node script that prepares the data, plus one HTML page that shows the result.

Data:
- Vektis open data file on the Health Insurance Act, per municipality (CSV, 2.9 MB):
  https://www.vektis.nl/vektisnl-assets/prd/storage/uploads/Docs%20per%20pagina/Open%20Data%20Bestanden/2024/Vektis%20Open%20Databestand%20Zorgverzekeringswet%202024%20-%20gemeente.csv
  Find the newest year by trying the same path with descending years (HEAD; 2025 returned 404 on 16 Oct 2026).
- CBS Key figures for districts and neighbourhoods 2026 (OData v4, CORS open):
  municipality codes: https://datasets.cbs.nl/odata/v1/CBS/86358NED/WijkenEnBuurtenCodes?$filter=DimensionGroupId eq 'GM'&$select=Identifier,Title
  residents: https://datasets.cbs.nl/odata/v1/CBS/86358NED/Observations?$filter=startswith(WijkenEnBuurten,'GM') and Measure eq 'T001036'&$select=WijkenEnBuurten,Value

The script (Node 20+, fetch) writes a JSON per municipality with: GM code, name, residents, insured years, costs per insured year (total and per cost category), the index after correcting for age and sex, and the rank. The page lets me pick a municipality and shows: costs per insured year with the rank, the index (Netherlands = 100), a bar chart per type of care (municipality against all municipalities together) and the five municipalities closest by index.

Pitfalls:
- The Vektis file sends no CORS header: the browser can't fetch it directly. Prepare it with the script (or a server) and serve the JSON yourself.
- Semicolons, dot as decimal mark, CRLF. 26 columns start with kosten_ (names truncated at 32 characters); add them all up for the total. Columns differ per year (mental health categories new from 2022): read the header, don't use fixed positions.
- The first data row is the residual category (sex, age and municipality empty; 412,394 insured). Count it for the Netherlands, not for any municipality.
- Divide by aantal_verzekerdejaren, not by aantal_bsn and not by residents.
- Municipalities are names in capitals without a code ('S GRAVENHAGE', 'BERGEN NH', 'BERGEN LB', 'NUENEN CA'). Normalise (strip accents, punctuation and spaces) and match to the 2026 CBS list; use the name without brackets only when unique; aliases BERGEN LB = Bergen (L.), NUENEN CA = Nuenen, Gerwen en Nederwetten. Check that all 342 match and report what doesn't.
- Age correction: national costs per insured year per sex x age band (without the residual category), times the municipality's insured years per cell, summed = expected. Index = actual / expected x 100.
- Age bands have double spaces ('0 t/m  4 jaar') and the last one is '90+': turn them into numbers for sorting.
- Costs are claims over eight quarters, negative amounts are corrections, specialist mental health care is about 90% complete. Only what goes through basic insurance: no social support (Wmo), no long-term care (Wlz), no supplementary insurance.
- Vektis terms: non-commercial use only, and put 'source: Vektis' under every chart.

Check: cost year 2024 gives 3,349 euros per insured year for the Netherlands (59,525 million euros, 17,773,377 insured years), Heerlen 4,520 euros with index 125, Utrecht 2,898 euros with index 102 (rank 333 uncorrected, 74 after correction).

Try it yourself

The core of the project in a small script. Needs: Node 20 of nieuwer (ingebouwde fetch), geen pakketten. Opslaan als zorgkosten.mjs en draaien met: node zorgkosten.mjs Utrecht. Tested on 2026-10-16; below is the real output of that run.

// Zorgkosten-vergelijker: kosten van de basisverzekering per verzekerdejaar per gemeente en per zorgsoort,
// met en zonder correctie voor leeftijd en geslacht. Bron: Vektis open databestand Zvw (gemeente, CSV) en
// CBS Kerncijfers wijken en buurten 2026 (86358NED: GM-codes en inwoners, indeling 2026 = indeling van het Vektis-bestand 2024).
// Vektis-voorwaarden: niet-commercieel gebruik, vermeld "bron: Vektis". Draai met: node zorgkosten.mjs [gemeentenaam]
const UA = { "User-Agent": "mijn-zorgkosten/1.0 (+jij@voorbeeld.nl)" };
const VEKTIS = (j) => `https://www.vektis.nl/vektisnl-assets/prd/storage/uploads/Docs%20per%20pagina/Open%20Data%20Bestanden/${j}/Vektis%20Open%20Databestand%20Zorgverzekeringswet%20${j}%20-%20gemeente.csv`;
const KWB = "https://datasets.cbs.nl/odata/v1/CBS/86358NED";
const MIJN = (process.argv[2] ?? "Utrecht").toUpperCase();
const euro = (x) => Math.round(x).toLocaleString("nl-NL");
const norm = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toUpperCase().replace(/[^A-Z0-9]/g, "");

// 1. Nieuwste kostenjaar: probeer het vaste pad met dalende jaartallen (HEAD). 2025 gaf op 16 okt 2026 nog 404.
let jaar = new Date().getFullYear();
while (!(await fetch(VEKTIS(jaar), { method: "HEAD", headers: UA })).ok) jaar--;
const tekst = await (await fetch(VEKTIS(jaar), { headers: UA })).text();
const regels = tekst.trim().split(/\r?\n/).map((r) => r.split(";"));
const kop = regels[0];
const iG = kop.indexOf("gemeentenaam"), iS = kop.indexOf("geslacht"), iL = kop.indexOf("leeftijdsklasse"), iVj = kop.indexOf("aantal_verzekerdejaren");
const kol = kop.map((k, i) => [k, i]).filter(([k]) => k.startsWith("kosten_"));
console.log(`Vektis ${jaar}: ${regels.length - 1} rijen, ${kol.length} kostenrubrieken, ${(tekst.length / 1e6).toFixed(1)} MB`);

// 2. Optellen. De eerste datarij (geen gemeente, geslacht of leeftijd) is de restcategorie: alleen landelijk meetellen.
const gem = new Map(), cel = new Map(); // cel = landelijk per geslacht x leeftijdsklasse (zonder restcategorie)
let nlEuro = 0, nlVj = 0;
const nlRub = Object.fromEntries(kol.map(([k]) => [k, 0])); // per rubriek, zonder restcategorie
let gemVj = 0;
for (const r of regels.slice(1)) {
  const vj = Number(r[iVj]);
  let tot = 0;
  for (const [k, i] of kol) tot += Number(r[i]) || 0;
  nlEuro += tot; nlVj += vj;
  if (!r[iG]) continue;
  gemVj += vj;
  for (const [k, i] of kol) nlRub[k] += Number(r[i]) || 0;
  const g = gem.get(r[iG]) ?? { euro: 0, vj: 0, rub: {}, cellen: [] };
  g.euro += tot; g.vj += vj; g.cellen.push([r[iS] + r[iL], vj]);
  for (const [k, i] of kol) g.rub[k] = (g.rub[k] ?? 0) + (Number(r[i]) || 0);
  gem.set(r[iG], g);
  const c = cel.get(r[iS] + r[iL]) ?? { euro: 0, vj: 0 };
  c.euro += tot; c.vj += vj; cel.set(r[iS] + r[iL], c);
}
console.log(`Nederland: ${euro(nlEuro / 1e6)} mln euro, ${euro(nlVj)} verzekerdejaren, ${euro(nlEuro / nlVj)} euro per verzekerdejaar (incl. restcategorie)`);

// 3. Correctie voor leeftijd en geslacht: wat zou de gemeente kosten met de landelijke bedragen per geslacht en leeftijdsklasse?
for (const g of gem.values()) {
  const verwacht = g.cellen.reduce((a, [k, vj]) => a + vj * (cel.get(k).euro / cel.get(k).vj), 0);
  g.perVj = g.euro / g.vj;
  g.index = (g.euro / verwacht) * 100;
}

// 4. Koppel de namen (hoofdletters, zonder code) aan CBS-gemeentecodes en inwoners op 1 januari 2026.
const codes = (await (await fetch(`${KWB}/WijkenEnBuurtenCodes?$filter=DimensionGroupId eq 'GM'&$select=Identifier,Title`, { headers: UA })).json()).value;
const inw = Object.fromEntries((await (await fetch(`${KWB}/Observations?$filter=startswith(WijkenEnBuurten,'GM') and Measure eq 'T001036'&$select=WijkenEnBuurten,Value`, { headers: UA })).json()).value.map((o) => [o.WijkenEnBuurten, o.Value]));
const opNaam = new Map(), kaal = new Map();
for (const c of codes) {
  opNaam.set(norm(c.Title), c);
  const k = norm(c.Title.replace(/\s*\(.*\)$/, "")); // "Beek (L.)" -> BEEK, maar alleen als dat uniek is
  kaal.set(k, kaal.has(k) ? null : c);
}
for (const [k, c] of kaal) if (c && !opNaam.has(k)) opNaam.set(k, c);
opNaam.set("BERGENLB", opNaam.get("BERGENL")); // Vektis: BERGEN LB = Bergen (L.)
opNaam.set("NUENENCA", opNaam.get("NUENENGERWENENNEDERWETTEN"));
const mis = [...gem.keys()].filter((n) => !opNaam.get(norm(n)));
for (const [n, g] of gem) g.cbs = opNaam.get(norm(n));
console.log(`Gekoppeld aan CBS: ${gem.size - mis.length} van ${gem.size} gemeenten${mis.length ? ` (niet: ${mis.join(", ")})` : ""}`);

// 5. Ranglijst en jouw gemeente.
const rij = [...gem.entries()].filter(([, g]) => g.cbs);
const rang = (f) => [...rij].sort((a, b) => f(b[1]) - f(a[1])).map(([n]) => n);
const rPer = rang((g) => g.perVj), rIdx = rang((g) => g.index);
const toon = (n) => { const g = gem.get(n); return `${g.cbs.Title.padEnd(26)} ${g.cbs.Identifier} ${euro(g.perVj).padStart(6)} euro/vj  index ${g.index.toFixed(0).padStart(3)}  (${euro(inw[g.cbs.Identifier])} inw.)`; };
console.log("\nHoogste kosten per verzekerdejaar (ongecorrigeerd):"); rPer.slice(0, 3).forEach((n) => console.log("  " + toon(n)));
console.log("Laagste:"); rPer.slice(-3).forEach((n) => console.log("  " + toon(n)));
console.log("Hoogste na correctie voor leeftijd en geslacht (index, Nederland = 100):"); rIdx.slice(0, 3).forEach((n) => console.log("  " + toon(n)));
console.log("Laagste na correctie:"); rIdx.slice(-3).forEach((n) => console.log("  " + toon(n)));

const m = gem.get(MIJN);
console.log(`\n${toon(MIJN)}\n  plaats ${rPer.indexOf(MIJN) + 1} van ${rij.length} ongecorrigeerd, ${rIdx.indexOf(MIJN) + 1} na correctie`);
const naam = (k) => k.replace(/^kosten_/, "").replace(/_/g, " ");
const verschil = kol.map(([k]) => [k, m.rub[k] / m.vj, nlRub[k] / gemVj]).filter(([, , n]) => n > 20).sort((a, b) => b[2] - a[2]).slice(0, 6);
console.log("  Grootste zorgsoorten, euro per verzekerdejaar (gemeente / alle gemeenten samen):");
for (const [k, g, n] of verschil) console.log(`    ${naam(k).padEnd(30)} ${euro(g).padStart(5)} / ${euro(n).padStart(5)}  (${(g / n * 100 - 100 >= 0 ? "+" : "") + (g / n * 100 - 100).toFixed(0)}%)`);
Output
Vektis 2024: 12992 rijen, 26 kostenrubrieken, 2.9 MB
Nederland: 59.525 mln euro, 17.773.377 verzekerdejaren, 3.349 euro per verzekerdejaar (incl. restcategorie)
Gekoppeld aan CBS: 342 van 342 gemeenten

Hoogste kosten per verzekerdejaar (ongecorrigeerd):
  Heerlen                    GM0917  4.520 euro/vj  index 125  (87.764 inw.)
  Landgraaf                  GM0882  4.520 euro/vj  index 120  (36.844 inw.)
  Brunssum                   GM0899  4.471 euro/vj  index 119  (27.474 inw.)
Laagste:
  Rozendaal                  GM0277  2.700 euro/vj  index  78  (1.837 inw.)
  Staphorst                  GM0180  2.641 euro/vj  index  86  (18.219 inw.)
  Urk                        GM0184  2.601 euro/vj  index  96  (22.431 inw.)
Hoogste na correctie voor leeftijd en geslacht (index, Nederland = 100):
  Heerlen                    GM0917  4.520 euro/vj  index 125  (87.764 inw.)
  Landgraaf                  GM0882  4.520 euro/vj  index 120  (36.844 inw.)
  Stadskanaal                GM0037  4.458 euro/vj  index 120  (32.110 inw.)
Laagste na correctie:
  Schiermonnikoog            GM0088  3.255 euro/vj  index  83  (972 inw.)
  Rozendaal                  GM0277  2.700 euro/vj  index  78  (1.837 inw.)
  Hilvarenbeek               GM0798  2.797 euro/vj  index  77  (16.315 inw.)

Utrecht                    GM0344  2.898 euro/vj  index 102  (378.121 inw.)
  plaats 333 van 342 ongecorrigeerd, 74 na correctie
  Grootste zorgsoorten, euro per verzekerdejaar (gemeente / alle gemeenten samen):
    medisch specialistische z      1.395 / 1.777  (-22%)
    farmacie                         278 /   362  (-23%)
    consulten ggz                    374 /   233  (+61%)
    verpleging en verzorging         117 /   184  (-37%)
    hulpmiddelen                      80 /   115  (-31%)
    huisarts overig                   96 /    96  (+1%)

Where it goes wrong

  • Without age correction you mostly measure ageing. Urk has the lowest costs per insured year (2,601 euros), but index 96 after correction: 34% of its insured years are under 20. Rozendaal and Hilvarenbeek stay low after correction too (78 and 77). Always show both figures.
  • The residual category (first data row, no municipality) belongs to the national total, not to a municipality. Leave it out and the national figure (3,349 euros) is wrong; add it to a municipality and you get a phantom municipality with 75.7 million euros in hospital costs.
  • Names in capitals without a code. Match with the 2026 municipal boundaries (the 2024 file uses those), not with 2024's. Two aliases are needed: BERGEN LB and NUENEN CA. Bergen (NH.) and Bergen (L.) are two different municipalities: never match on 'Bergen' alone.
  • The CSV has no CORS header: a web page can't fetch it on its own. Process it beforehand (script, build step or your own server) and deliver a small JSON to the browser.
  • Columns change per year: 2011 had upper case and full names (KOSTEN_TWEEDELIJNS_GGZ), from 2022 there are three new mental health categories. For a series over years, read the header per year and compare only the total or categories that really mean the same thing.
  • Basic insurance only. Social support (Wmo), long-term care (Wlz, nursing homes) and supplementary insurance are not included. A municipality with many people in nursing homes may therefore show low district nursing costs. Call it 'basic insurance healthcare costs', not 'all healthcare costs'.
  • Terms: Vektis allows personal use and research only, not commercial, with the credit 'source: Vektis'. A paid app or a company's customer portal doesn't qualify.

Take it further

  • Map of the Netherlands with the index per municipality: get the 2026 municipal boundaries from PDOK or use 2025's and report the differences. Colour around 100 with a diverging scale.
  • Time series 2017 to 2024 per municipality from the yearly files, only for the total and categories that didn't change name (pharmacy, GP, hospital care). Watch municipal mergers: a 2024 municipality sometimes didn't exist yet in 2017.
  • Zoom in with the postcode3 file (21.8 MB, age per year): healthcare costs per postcode area within your municipality. Join with income per postcode (CBS grid statistics) and check whether mental health costs relate to income.
  • Put the corrected index next to lifestyle and health per municipality (GGD health monitor, RIVM) or next to the share of low-income residents from the key figures. Say that correlation is not causation.

More with this data

Basic health insurance costs by type of care: most Specialist medical care (31,273 million euros), then Pharmaceuticals and Mental health, consultations

Basic health insurance costs by type of care, 2024, in million euros. Total: 59,525; the largest 12 of 25 categories are shown. Specialist medical care is 53% of the total. Claimed costs under the basic health insurance, including the residual category (groups smaller than ten and unknown characteristics). 17,773,377 insured years.

Source: Vektis, Open Databestand Zorgverzekeringswet 2024 (gemeente); bron: Vektis · opgehaald 25 oktober 2026 · selection: kostenjaar 2024; basisverzekering (Zvw), alle kostenrubriekenOpen the table Single block

The sources3

Vektis open data on healthcare costs

Vektis, namens de zorgverzekeraarsNetherlands, postcode (3 digits) and municipality

What basic health insurance pays out per year, split into 26 cost types such as hospital care, medicines, GP and mental health. Per municipality (age in 5-year bands) and per three-digit postcode area (per year of age), for all insured people. Cost years 2011 to 2024.

OpenDownloadHealthFinance
Up to date
updated in July 2026

Key figures for districts and neighbourhoods

CBSNetherlands, neighbourhood

Over a hundred figures per neighbourhood: residents by age, households, property values, income, benefits, cars and distance to amenities. A new table every year.

OpenAPIDownloadPopulationHousing and landEconomy
Up to date
updated yesterday

Waarstaatjegemeente.nl (municipal benchmark)

VNG, gebouwd door ABF ResearchNetherlands, municipality, district and neighbourhood

VNG's data hub on all Dutch municipalities. Figures from public sources and from municipalities themselves, in dashboards on social services, housing, energy, safety, finances and more. In the database you build your own tables and maps and export the figures. The publication calendar lists when each source gets new figures.

OpenDownloadAPIGovernmentHealthHousing and land
Up to date
updated 17 hours ago

More to build