303,525 datasets indexed1,419 publishers
Open Data IndexThe index of open data about the Netherlands
Open Data Index›Build›Council decision alert: a ping when your municipality decides something
AlertGetting startedGovernment

Council decision alert: a ping when your municipality decides something

A script that searches your municipality's official announcements for your topic every day, reports only what is new, and adds what the council discussed about it earlier.

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("raadsbesluiten-alert")
  • geef_bron("overheid-sru")
  • geef_bron("openraadsinformatie")
The assistant

I have the project, the tested calls and the pitfalls. I'll build a alert with Overheid.nl search service (SRU) and Open Council Information. 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
MailboxSearch mail
RaadsalertUtrecht · parking: nothing new
RaadsalertUtrecht · parking: 56 new notices

Utrecht · parking: 56 new notices

RRaadsalert <alert@jouwsite.nl> · to me
Gemeenteblad Utrechtkeyword 'parkeren' · since 1 Sep
56new54traffic orders2by-laws

Rules first

  • Algemene plaatselijke verordening Utrecht 2010gmb-2026-474587
  • Aanwijzingsbesluit parkeerapparatuurplaatsen gemeente Utrechtgmb-2026-410813

Traffic orders 54all 54 →

  • 2026-10-11 Cornelis Houtmanstraat, Noordoost, Intrekken Parkeerverbod, Verkeersmaatregel
  • Onderwerp verkeersbesluit: het aanleggen van een gereserveerde gehandicaptenparkeerplaat…
Note: Open Raadsinformatie is behind

Newest Utrecht document: 7 July 2026, 102 days old. 7,063 council papers mention 'parkeren'.

You follow Utrecht + 'parkeren'. Next time only what is new.
Mockup

This is a mockup, not a working version, with the notices from our test run for Utrecht and 'parkeren' on 16 October 2026 (Gemeenteblad via the overheid.nl SRU search service, and Open Raadsinformatie). Turn the MCP server on with the address on For AI.

Who it's for

Residents and neighbourhood groups following a plan, local journalists, advocacy groups, council members and businesses who need to know when a bylaw or permit is published.

What you get

A small script (or a daily job in GitHub Actions, a cron or a Slack bot) that shows, per municipality and keyword, what is new in the Municipal Gazette, rules such as bylaws first, with a link to the official text. It remembers what you saw, so the second time it reports only new publications. Below that the newest council documents on the same word from Open Raadsinformatie, with a warning when that index is behind.

Test run on 16 October 2026 for Utrecht and 'parkeren' (parking) since 1 September 2026: 56 announcements in the Municipal Gazette, 54 of them traffic decisions and 2 bylaws (the general local bylaw on 12 October and the designation of parking meter locations on 1 September). A second run reported 0 new. Open Raadsinformatie had 7,063 Utrecht council documents with 'parkeren', but the newest document in the Utrecht index was discussed on 7 July 2026: 102 days old.

Official publications by gazette: most Gemeenteblad (municipal gazette) (560,560 publications), then Staatscourant (government gazette) and Waterschapsblad (water board gazette)

Official publications by gazette, 2025, in publications. Total: 695,682; the largest 12 of 13 categories are shown. Gemeenteblad (municipal gazette) is 81% of the total. By publication date (dcterms.available). The publications add up exactly to the total.

Source: KOOP, Zoekdienst Overheid.nl (SRU 1.2, officiële publicaties) · opgehaald 25 oktober 2026 · selection: x-connection=oep (officiële publicaties); publicatiedatum in 2025Open the table Single block

How you could start6 steps

  1. 1

    Search your municipality's Gazette

    KOOP's SRU search service (version 1.2) searches all official announcements. x-connection=oep is the announcements collection; the query is CQL. dcterms.creator is the municipality's name, publicationname the gazette, dcterms.available the publication date and keyword searches the full text. Use zoekdienst.overheid.nl: the SRU 2.0 addresses exclude everything in robots.txt.

    https://zoekdienst.overheid.nl/sru/Search?version=1.2&operation=searchRetrieve&x-connection=oep&startRecord=1&maximumRecords=1&query=dcterms.creator%3D%3D"Utrecht" and dcterms.available>%3D2026-09-01 and keyword%3D"parkeren"
    <searchRetrieveResponse …><version>1.2</version><numberOfRecords>56</numberOfRecords><records><record>…
      <dcterms:title>Aanwijzingsbesluit parkeerapparatuurplaatsen gemeente Utrecht</dcterms:title>
      <dcterms:identifier>gmb-2026-410813</dcterms:identifier>
      <dcterms:type scheme="overheidop:Gemeenteblad">algemeen verbindend voorschrift (verordening)</dcterms:type>
      <dcterms:creator>Utrecht</dcterms:creator> … <dcterms:available>2026-09-01</dcterms:available> …
      <url>https://zoek.officielebekendmakingen.nl/gmb-2026-410813.html</url>
  2. 2

    Note: the province is also called Utrecht

    dcterms.creator is only a name. Without publicationname, "Utrecht" also finds the Provincial Gazette of the province of Utrecht (identifier prb-…): 429 hits since 1 October against 368 with publicationname=="Gemeenteblad". Put the gazette in the query and also check the gmb- prefix (Groningen and Utrecht share their name with their provinces).

    https://zoekdienst.overheid.nl/sru/Search?version=1.2&operation=searchRetrieve&x-connection=oep&startRecord=1&maximumRecords=1&query=dcterms.creator%3D%3D"Utrecht" and dcterms.available>%3D2026-10-01
    <numberOfRecords>429</numberOfRecords> …
      <dcterms:title>Besluit aangevraagde omgevingsvergunning kabel- en leidingenactiviteit - N225 Utrechtsestraatweg ter hoogte van hm 36,2 in Rhenen</dcterms:title>
      <dcterms:identifier>prb-2026-16842</dcterms:identifier>
      <dcterms:type scheme="overheidop:Provinciaal blad">omgevingsvergunning</dcterms:type>
  3. 3

    Fetch everything and remember what you saw

    With maximumRecords up to 1,000 and startRecord you paginate. The order is not by date (sortKeys was silently ignored): sort yourself. Store the identifiers (gmb-2026-410813) in a file or database; anything not yet in it is new. Errors come as diagnostics XML with HTTP 200; an 'Unexpected HTTP status 500' succeeded on a second try on 16 October 2026.

    https://zoekdienst.overheid.nl/sru/Search?version=1.2&operation=searchRetrieve&x-connection=oep&startRecord=1&maximumRecords=1&query=dcterms.creator%3D%3D"Utrecht" and publicationname%3D%3D"Gemeenteblad" and dcterms.available>%3D2026-10-01
    <numberOfRecords>368</numberOfRecords><records><record>…
      <dcterms:title>Verleende Omgevingsvergunning, het kappen en herplanten van twee bomen, Kanaalweg achter hsnr 50, Merwedekanaalzone, GU-Z2026-0062296</dcterms:title>
      <dcterms:identifier>gmb-2026-467876</dcterms:identifier>
      <dcterms:type scheme="overheidop:Gemeenteblad">omgevingsvergunning</dcterms:type> …
    </record></records><nextRecordPosition>2</nextRecordPosition></searchRetrieveResponse>
  4. 4

    Find your municipality's index in Open Raadsinformatie

    Open Raadsinformatie (Open State Foundation) is an Elasticsearch with one index per municipality: ori_<name>_<timestamp>. Amsterdam districts have their own index, provinces start with osi_. No key, no robots.txt.

  5. 5

    First check how fresh the index is

    The highest last_discussed_at tells you until when the index was updated. Cap it at 'now': there are also dates in 2098. On 16 October 2026 the newest Utrecht document was from 7 July 2026, and across all indexes only 249 documents had a date after 1 August 2026. So use Open Raadsinformatie for background, not as the source of your alert.

    curl -X POST "https://api.openraadsinformatie.nl/v1/elastic/ori_utrecht_*/_search" \
      -H 'Content-Type: application/json' -d '{"size":0,"query":{"range":{"last_discussed_at":{"lte":"now"}}},"aggs":{"max":{"max":{"field":"last_discussed_at"}}}}'
    {"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},
     "hits":{"total":{"value":10000,"relation":"gte"},"max_score":null,"hits":[]},
     "aggregations":{"max":{"value":1.7834616E12,"value_as_string":"2026-07-07T22:00:00.000000"}}}
  6. 6

    Search the council documents on your topic

    Documents (@type MediaObject) have the full text in text and md_text. Ask only for the fields you need with _source, otherwise you get whole PDF texts back. Sort on last_discussed_at. original_url points to the document in the municipality's council information system (here iBabs).

    curl -X POST "https://api.openraadsinformatie.nl/v1/elastic/ori_utrecht_*/_search" \
      -H 'Content-Type: application/json' -d '{"size":2,"_source":["name","last_discussed_at","@type","original_url"],"query":{"bool":{"must":[{"match":{"text":"parkeren"}}],"filter":[{"term":{"@type":"MediaObject"}}]}},"sort":[{"last_discussed_at":"desc"}]}'
    {"took":18,…,"hits":{"total":{"value":7063,"relation":"eq"},"max_score":null,"hits":[
      {"_index":"ori_utrecht_20250705061904","_type":"_doc","_id":"7947074","_score":null,"_source":{
        "original_url":"https://api1.ibabs.eu/publicdownload.aspx?site=Utrecht&id=779bcccf-8fcb-485a-8e73-248aeeb8e9f1",
        "@type":"MediaObject","name":"Voorjaarsnota 2026","last_discussed_at":"2026-07-02T11:30:00+00:00"},"sort":[1782991800000]}, …

How the sources fit together

  • Overheid.nl search service (SRU)Open Council Informationvia gemeentenaam (dcterms.creator) en de indexnaam ori_<naam>_…, plus het zoekwoord

    There is no shared key between an announcement and the council document behind it. Join on municipality and keyword, and search the title of a bylaw (for example 'Algemene plaatselijke verordening') in the council documents around the publication date: the council proposal and decision are usually there a few weeks earlier.

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.

Write a Node script (20+, built-in fetch only) that I run every morning (cron or GitHub Actions) and that tells me what my municipality newly publishes on a topic, with what the council discussed about it earlier below.

Source 1, official announcements via SRU 1.2 (no key; only use this address, the SRU 2.0 hosts exclude everything in robots.txt):
https://zoekdienst.overheid.nl/sru/Search?version=1.2&operation=searchRetrieve&x-connection=oep&startRecord=1&maximumRecords=1000&query=dcterms.creator=="Utrecht" and publicationname=="Gemeenteblad" and dcterms.available>=2026-09-01 and keyword="parkeren"
- The response is XML. Per record: dcterms:title, dcterms:identifier (gmb-2026-410813), dcterms:type (type, for example 'verkeersbesluit of -mededeling', 'algemeen verbindend voorschrift (verordening)', 'omgevingsvergunning'), dcterms:available (date), and in enrichedData the url on zoek.officielebekendmakingen.nl.
- Errors come as <diagnostics> with HTTP 200. Check for that; on 'Unexpected HTTP status 500' retry after 5 and 10 seconds.
- maximumRecords at most 1,000; paginate with startRecord up to numberOfRecords.
- The order is not by date; sort yourself. sortKeys is ignored.
- Field names are quirky: publicationname (not w.publicatienaam), dcterms.available (not dt.available). keyword searches the full text.
- dcterms.creator is a name: without publicationname, "Utrecht" also finds the province (prb-…). Only keep identifiers starting with gmb-.
- Store the identifiers you already reported (JSON file or SQLite) and only report new ones. Put rules (type containing 'verordening' or 'beleidsregel') first.

Source 2, Open Raadsinformatie (Elasticsearch, no key): POST https://api.openraadsinformatie.nl/v1/elastic/ori_utrecht_*/_search
- Find the index name: GET https://api.openraadsinformatie.nl/v1/elastic/_cat/indices?format=json (ori_<name>_<timestamp>).
- Freshness: {"size":0,"query":{"range":{"last_discussed_at":{"lte":"now"}}},"aggs":{"max":{"max":{"field":"last_discussed_at"}}}}. On 16 October 2026 the index was more than three months behind (newest document 7 July 2026). Show a warning when the newest document is older than 30 days.
- Search: {"size":5,"_source":["name","last_discussed_at","original_url"],"query":{"bool":{"must":[{"match":{"text":"parkeren"}}],"filter":[{"term":{"@type":"MediaObject"}}]}},"sort":[{"last_discussed_at":"desc"}]}. Never request text or md_text unless needed: those are whole PDFs.

Output: a short text (also suitable for email or Slack) with, per new announcement, date, type, title and link; below that the five newest council documents with date and link, and the freshness warning. Settings (municipality, index name, words, start date) in a config file; several words possible.

Try it yourself

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

// Raadsbesluiten-alert: nieuwe officiële bekendmakingen van je gemeente over een onderwerp (Gemeenteblad, via SRU),
// plus wat er in de raadsstukken over staat (Open Raadsinformatie). Draai dagelijks; het onthoudt wat je al zag.
// Bronnen: zoekdienst.overheid.nl (SRU 1.2) en api.openraadsinformatie.nl (Elasticsearch). Geen sleutels. Node 20+.
import { existsSync, readFileSync, writeFileSync } from "node:fs";
const UA = { "User-Agent": "mijn-project/1.0 (+jij@voorbeeld.nl)" };
const GEMEENTE = process.argv[2] ?? "Utrecht";     // zoals in dcterms.creator
const ORI_INDEX = process.argv[3] ?? "utrecht";    // ori_<naam>_<tijdstempel>; zie _cat/indices
const WOORD = process.argv[4] ?? "parkeren";
const VANAF = process.argv[5] ?? "2026-09-01";

// 1. Gemeenteblad via SRU. Fouten komen als diagnostics-XML met HTTP 200; een losse 'Unexpected HTTP status 500' ging
//    bij een tweede poging goed, dus probeer het een paar keer.
const veld = (r, n) => (r.match(new RegExp(`<${n}[^>]*>([^<]*)</${n}>`)) ?? [])[1] ?? "";
async function sru(q) {
  for (let poging = 1; ; poging++) {
    const xml = await (await fetch(`https://zoekdienst.overheid.nl/sru/Search?${q}`, { headers: UA })).text();
    if (!xml.includes("<diagnostics")) return xml;
    if (poging === 3) throw new Error("SRU-fout: " + veld(xml, "message") + " " + veld(xml, "details"));
    await new Promise((r) => setTimeout(r, 5000 * poging));
  }
}
const records = [];
for (let start = 1; ; start += 1000) {                    // oep: hoogstens 1.000 per verzoek
  const xml = await sru(new URLSearchParams({ version: "1.2", operation: "searchRetrieve", "x-connection": "oep", startRecord: start, maximumRecords: 1000,
    query: `dcterms.creator=="${GEMEENTE}" and publicationname=="Gemeenteblad" and dcterms.available>=${VANAF} and keyword="${WOORD}"` }));
  records.push(...xml.split("<record>").slice(1).map((r) => ({ id: veld(r, "dcterms:identifier"), datum: veld(r, "dcterms:available"),
    soort: veld(r, "dcterms:type"), titel: veld(r, "dcterms:title").trim(), url: veld(r, "url") })));
  if (start + 1000 > +veld(xml, "numberOfRecords")) break;
}
// creator=="Utrecht" vindt zonder publicationname óók de provincie Utrecht (prb-…): controleer het voorvoegsel gmb-.
const gmb = records.filter((r) => r.id.startsWith("gmb-")).sort((a, b) => b.datum.localeCompare(a.datum));   // SRU sorteert niet op datum
const BESTAND = `gezien-${GEMEENTE.toLowerCase()}-${WOORD}.json`;
const gezien = new Set(existsSync(BESTAND) ? JSON.parse(readFileSync(BESTAND, "utf8")) : []);
const nieuw = gmb.filter((r) => !gezien.has(r.id));
writeFileSync(BESTAND, JSON.stringify(gmb.map((r) => r.id)));
const perSoort = {};
for (const r of gmb) perSoort[r.soort] = (perSoort[r.soort] ?? 0) + 1;
console.log(`Gemeenteblad ${GEMEENTE}, '${WOORD}', sinds ${VANAF}: ${gmb.length} bekendmakingen, ${nieuw.length} nieuw sinds de vorige keer`);
for (const [s, n] of Object.entries(perSoort).sort((a, b) => b[1] - a[1])) console.log(`  ${String(n).padStart(3)}  ${s}`);
// Regels (verordeningen, beleidsregels) eerst: die stelt de raad of het college vast. Daarna de rest.
const regel = (r) => /verordening|beleidsregel/i.test(r.soort);
const toon = [...nieuw.filter(regel), ...nieuw.filter((r) => !regel(r)).slice(0, 2)];
for (const r of toon) console.log(`  ${r.datum}  ${r.titel.slice(0, 88)}\n              ${r.url}`);

// 2. Raadsstukken in Open Raadsinformatie: hoe vers is de index, en wat zijn de nieuwste stukken over het woord?
const ori = async (body) => (await fetch(`https://api.openraadsinformatie.nl/v1/elastic/ori_${ORI_INDEX}_*/_search`, {
  method: "POST", headers: { ...UA, "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
const vers = await ori({ size: 0, aggs: { max: { max: { field: "last_discussed_at" } } }, query: { range: { last_discussed_at: { lte: "now" } } } });
const tot = vers.aggregations.max.value_as_string.slice(0, 10);
const dagen = Math.round((Date.now() - Date.parse(tot)) / 864e5);
console.log(`\nOpen Raadsinformatie ori_${ORI_INDEX}: nieuwste stuk besproken op ${tot} (${dagen} dagen geleden)${dagen > 30 ? "  LET OP: index loopt achter" : ""}`);
const hits = await ori({ size: 3, _source: ["name", "last_discussed_at", "@type", "original_url"],
  query: { bool: { must: [{ match: { text: WOORD } }], filter: [{ term: { "@type": "MediaObject" } }] } },
  sort: [{ last_discussed_at: "desc" }] });
console.log(`Raadsstukken met '${WOORD}': ${hits.hits.total.value}${hits.hits.total.relation === "gte" ? "+" : ""}, nieuwste:`);
for (const h of hits.hits.hits) console.log(`  ${h._source.last_discussed_at.slice(0, 10)}  ${h._source.name}`);
Output
Gemeenteblad Utrecht, 'parkeren', sinds 2026-09-01: 56 bekendmakingen, 56 nieuw sinds de vorige keer
   54  verkeersbesluit of -mededeling
    2  algemeen verbindend voorschrift (verordening)
  2026-10-12  Algemene plaatselijke verordening Utrecht 2010
              https://zoek.officielebekendmakingen.nl/gmb-2026-474587.html
  2026-09-01  Aanwijzingsbesluit parkeerapparatuurplaatsen gemeente Utrecht
              https://zoek.officielebekendmakingen.nl/gmb-2026-410813.html
  2026-10-13  2026-10-11 Cornelis Houtmanstraat, Noordoost, Intrekken Parkeerverbod, Verkeersmaatregel
              https://zoek.officielebekendmakingen.nl/gmb-2026-477731.html
  2026-10-13  Onderwerp verkeersbesluit: het aanleggen van een gereserveerde gehandicaptenparkeerplaat
              https://zoek.officielebekendmakingen.nl/gmb-2026-478013.html

Open Raadsinformatie ori_utrecht: nieuwste stuk besproken op 2026-07-07 (102 dagen geleden)  LET OP: index loopt achter
Raadsstukken met 'parkeren': 7063, nieuwste:
  2026-07-02  Voorjaarsnota 2026
  2026-07-02  Coalitieakkoord 'Voor Uuuu' 2026-2030, gemeente Utrecht
  2026-07-01  Raadsbrief Besluit tot intrekking van de lopende prijsvaststellingsprocedure van kantoor Winthontlaan 1 te Utrecht in MWKZ

Where it goes wrong

  • On 16 October 2026 Open Raadsinformatie was more than three months behind: the newest Utrecht document was from 7 July 2026 and across all indexes only 249 documents had a date after 1 August. Build your alert on the Municipal Gazette and use council documents as background, with a freshness check.
  • dcterms.creator is a name, not a code. 'Utrecht' and 'Groningen' are also provinces (prb-…), and some municipality names are spelled differently than you expect. Put publicationname=="Gemeenteblad" in the query and check the gmb- prefix.
  • SRU returns errors as diagnostics XML with HTTP 200. A wrong field name gives 'Invalid search field', a temporary failure 'Unexpected HTTP status 500' or 'Socket timeout exception' (both seen on 16 and 17 October 2026, fine again a few minutes later). Always check for <diagnostics> before reading numberOfRecords, otherwise you think there is nothing new.
  • keyword searches the full text and is broad: 'warmtenet' (heat network) also found permits for ground-source energy and discharges in Utrecht. Show type and title so readers can filter quickly, and allow several or more precise words.
  • Traffic decisions and permits make up most announcements (54 of 56 for 'parkeren'). You recognise a council decision by the type 'algemeen verbindend voorschrift (verordening)' or 'beleidsregel'; put those first or make a separate filter.
  • Do not use other entry points that robots.txt excludes: open.overheid.nl (FOI documents), repository.overheid.nl (the documents themselves) and the SRU 2.0 hosts. Link to zoek.officielebekendmakingen.nl for the reader, but do not let your script fetch those pages.

Take it further

  • Search by location instead of word: some announcements have coordinates in dcterms:spatial (RD), many have a street name in the title. Report everything within 500 metres of your address.
  • Follow the rules in force: the cvdr collection (x-connection=cvdr) holds the local regulations themselves. Show when a bylaw you follow gets a new version.
  • Turn it into a Slack or Telegram bot that follows one municipality and a list of words per channel, and posts new publications once a day.
  • Compare municipalities: count bylaws and policy rules per month per municipality (maximumRecords=0 gives only the count) and see who regulates most.

More with this data

Council and committee meetings recorded in Open Raadsinformatie: 20,868 meetings per year in 2025, 3.8 times as much as in 2010

Council and committee meetings recorded in Open Raadsinformatie, 2010 – 2025, in meetings per year. The highest point was 2021 (21,656).

Source: Open State Foundation, Open Raadsinformatie (Elasticsearch-API) · opgehaald 25 oktober 2026 · selection: @type: Meeting; naar startdatum; alleen hele jarenOpen the table Single block

The sources2

Overheid.nl search service (SRU)

KOOP (Kennis- en exploitatiecentrum Officiële Overheidspublicaties)Netherlands, publication or regulation

The search service behind overheid.nl: one SRU address for official announcements (Government Gazette, municipal gazettes, parliamentary papers …), local regulations of municipalities, provinces and water boards, the laws in force and the products and services of government bodies. On 16 October 2026 it held 6.7 million official publications, 344,109 local regulations, 148,592 law texts and 54,667 products. You get metadata in XML with a link to the document.

OpenAPISearch serviceGovernment
Up to date
updated 15 hours ago

Open Council Information

Open State Foundation en VNGNetherlands, municipality

Agendas, motions, proposals and minutes from hundreds of municipalities in one searchable index, with an open search API.

OpenSearch serviceAPIGovernment
Reachable
daily

More to build