# NL CAO Wijzigingen Database (`codeclouds/nl-cao-wijzigingen-database`) Actor

Dutch collective labour agreement (CAO) changes, extracted from official Staatscourant algemeenverbindendverklaring publications. Structured per sector, with wage increase and validity extraction.

- **URL**: https://apify.com/codeclouds/nl-cao-wijzigingen-database.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** Other, Jobs
- **Stats:** 2 total users, 0 monthly users, 88.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 cao-records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NL CAO Wijzigingen Database 🇳🇱

Track **Dutch collective labour agreement (CAO) changes** as a structured, machine-readable feed — extracted directly from official Staatscourant *algemeenverbindendverklaring* (avv) publications.

Every year, the Ministry of Social Affairs and Employment (SZW) publishes hundreds of decisions that make CAO provisions binding for an entire sector — new agreements, amendments, extensions, and revocations. Today, that information is only available through slow, API-less web portals (caobank.szw.nl, AWVN Cao-kijker, CAOWijzer) built for human browsing. This actor turns that public stream into clean, structured data: per-sector CAO name, publication type, validity period, and wage-increase percentages where mentioned in the text.

### Who is this for?

- **Payroll & HR software vendors** who need to track CAO updates across sectors instead of following one at a time
- **Salarisadministratiekantoren** monitoring multiple client sectors for wage table changes
- **Employer & employee organisations** tracking sector-wide developments beyond their own CAO
- **Legal & compliance teams** who need an audit trail of avv decisions affecting a sector
- **Market researchers** analysing Dutch wage trends across industries
- **AI agents** — clean, flat JSON output, ideal as a tool for agentic workflows (MCP-ready)

### When should an AI agent use this?

- "Has the CAO for Bouw & Infra changed recently, and what's the new wage increase?"
- "Which CAOs got a wijziging (amendment) or verlenging (extension) published in the Staatscourant this month?"
- "What's the current looptijd (validity period) of the Sociaal Werk, Welzijn & Maatschappelijke Dienstverlening CAO?"
- "Are there any pending avv requests (verzoek) for the Metaal sector that haven't been decided yet?"
- "List all new algemeenverbindendverklaring decisions (nieuwe\_avv) published since January 2026."
- "Did any CAO wage-increase percentage change for a sector I'm already tracking, compared to last week's run?"

### What data do you get?

Each result is one Staatscourant avv-publication:

```json
{
  "id": "stcrt-2025-6395",
  "title": "Besluit van de Minister van Sociale Zaken en Werkgelegenheid van 19 maart 2025 tot wijziging van het besluit tot algemeenverbindendverklaring van bepalingen van de collectieve arbeidsovereenkomst Sociaal Werk, Welzijn & Maatschappelijke Dienstverlening",
  "recordType": "wijziging",
  "caoNaam": "Sociaal Werk, Welzijn & Maatschappelijke Dienstverlening",
  "ministerie": "Ministerie van Sociale Zaken en Werkgelegenheid",
  "publicatiedatum": "2025-03-25",
  "looptijd": "2025",
  "loonsverhogingen": [
    { "percentage": 1.13, "datum": "1 januari 2025" }
  ],
  "onderwerpen": ["loon", "pensioen"],
  "partijen": ["FNV", "CNV Vakmensen"],
  "citaat": "… de pensioenpremie wordt met ingang van 1 januari 2025 verhoogd naar 25% …",
  "urlHtml": "https://repository.overheid.nl/frbr/officielepublicaties/stcrt/2025/stcrt-2025-6395/1/html/stcrt-2025-6395.html",
  "urlXml": "https://repository.overheid.nl/frbr/officielepublicaties/stcrt/2025/stcrt-2025-6395/1/xml/stcrt-2025-6395.xml",
  "bron": "officielebekendmakingen",
  "sectorEerderGezien": false,
  "isWijzigingSignaal": true,
  "vorigLoonpercentage": 0.75,
  "loonTrendDelta": 0.38
}
```

- **`recordType`** is derived from the publication's fixed legal phrasing — `nieuwe_avv` (fresh decision), `wijziging` (amendment), `verlenging` (extension), `intrekking` (revocation), or `verzoek` (pending request, decision not yet made)
- **`caoNaam`** is the sector/CAO name — extracted from the title, or (for pending `verzoek` publications, which carry no sector name in the title) from the full-text body
- **`looptijd`** and **`loonsverhogingen`** are best-effort extractions from the official full text — not every publication mentions a wage percentage (some only touch e.g. pension contributions), so these can be empty
- **`onderwerpen`** is a curated keyword classification of the full text on subject (`loon`, `pensioen`, `werktijden`, `verlof`, `scholing`) — so a change that's *not* about wages (e.g. only pension contributions) is still recognisable instead of looking like an empty, uninformative result
- **`partijen`** is a best-effort recognition of known employer/employee organisation names in the full text (e.g. `FNV`, `CNV Vakmensen`, `VNO-NCW`) — an empty array means none of the known names were recognised, not that there are no parties
- **`citaat`** is a short quote from the full text around the first matched `onderwerpen` keyword, so you can see the actual supporting text without opening `urlHtml` yourself. `null` if no subject was recognised
- **`isWijzigingSignaal`** is `true` for an explicit amendment/extension, or for a fresh avv covering a sector you've already seen in a previous run (only meaningful with `incrementalMode`)
- **`vorigLoonpercentage`/`loonTrendDelta`** (only meaningful with `incrementalMode`): compares this publication's first wage-increase percentage against the last known percentage for the same sector from a previous run. Both are `null` if either value is unknown — never a guessed `0`

### Input options

| Field | Description | Example |
|---|---|---|
| `sectorKeyword` | Free-text filter on the full-text index. Known sector aliases (e.g. `"thuiszorg"`/`"VVT"`) are automatically expanded to all known variants of the same sector, so a different spelling than the official title doesn't miss results | `"Bouw"`, `"Metaal"`, `"Horeca"` |
| `publishedAfter` | Only publications from this date | `"2026-01-01"` |
| `recordTypes` | Which publication types to include | `["nieuwe_avv", "wijziging"]` |
| `maxResults` | Cap the number of results | `100` |
| `incrementalMode` | Only new items since previous run + flag previously-seen sectors | `true` |
| `alleenSignalen` | Only deliver (and charge for) records where `isWijzigingSignaal` is true or `loonTrendDelta` is known — lowers the number of `cao-record` charges for clients who only care about detected changes/trends, not every base publication | `true` |

### Typical use cases

**Daily monitoring feed for a payroll provider, wijziging-alerts only:**

```json
{
  "recordTypes": ["wijziging", "verlenging"],
  "incrementalMode": true
}
```

**Track one sector's CAO history:**

```json
{
  "sectorKeyword": "Bouw & Infra",
  "maxResults": 50
}
```

**All new avv decisions since a given date:**

```json
{
  "recordTypes": ["nieuwe_avv"],
  "publishedAfter": "2026-01-01"
}
```

### Run summary (key-value store, free)

At the end of each run, a `RUN_SUMMARY` object is written to the actor's key-value store — no extra charge.
It includes the total records delivered, alert/trend counts, how many records were skipped by
`alleenSignalen`, and `gemiddeldeLoonTrendDelta`: the average wage-trend delta across all sectors with a known
trend this run (`null` if none had one — never a guessed `0`). Useful for a quick "how many sectors moved, and
by how much on average" overview without aggregating the dataset yourself.

### Scheduling & integrations

- **Schedules** — run weekly and get only new/changed CAO publications
- **Integrations** — push results to Google Sheets, Slack, Zapier, Make, or any webhook
- **API** — fetch the dataset as JSON, CSV or Excel from your own code
- **MCP** — expose this actor as a tool for AI agents (Claude, Cursor, and other MCP clients)

### Data source & legality

This actor uses the **official public SRU API** of the Dutch government (KOOP / repository.overheid.nl), the same source behind zoek.officielebekendmakingen.nl and the Staatscourant.

- ✅ No scraping of protected websites — official open data API only, no login, no ToS conflicts
- ✅ Sector-wide agreements between employer and employee organisations — no personal data, no natural persons identified
- ⚠️ `looptijd` and `loonsverhogingen` are extracted from free text with regular expressions. They are informational — always consult the official publication (`urlHtml`) for legally binding terms

### Pricing

Pay per result: you are only charged for CAO records actually delivered, plus a small extra charge for each detected wijziging-alert (an amendment, extension, or a fresh avv for a sector you've seen before). With `incrementalMode` enabled, a record where a loon-trend comparison could be made (both `vorigLoonpercentage` and this record's wage percentage are known) is additionally billed at the **`loon-trend-signaal`** rate.

### FAQ

**How fresh is the data?** Publications appear in the feed the same day SZW publishes them in the Staatscourant.

**Why is `caoNaam` sometimes empty?** A small number of publications use non-standard title phrasing that doesn't match the sector-name pattern, and the full-text fallback also found nothing. This is rare — check `urlHtml` for the source in that case.

**Why is `loonsverhogingen` often empty?** Not every avv decision changes wages — some only extend a CAO's term, or update non-wage clauses (pension contributions, working hours). An empty array is a normal, expected result.

**How do I get only real changes, not pending requests?** Set `recordTypes` to exclude `"verzoek"` (a `verzoek` is a request awaiting a ministerial decision, not yet a finalized CAO change).

**How does the loon-trend comparison work across runs?** With `incrementalMode` on, the actor remembers the last-seen wage percentage per sector in its key-value store. The next time that sector appears, `vorigLoonpercentage` and `loonTrendDelta` are filled in — the very first time a sector is seen, both are `null` (nothing to compare against yet).

### Related Actors

Also by this developer — Dutch/EU public-data actors that pair well with this one:

- **[NL Vergunningen & Bekendmakingen Leadfeed](https://apify.com/codeclouds/nl-vergunningen-leadfeed)** — same official publication family, turned into commercial leads instead of HR/legal tracking.
- **[NL & EU Government Tenders Scraper](https://apify.com/codeclouds/nl-eu-tenders-scraper)** — same "official government publication → structured feed" approach, applied to public procurement tenders.
- **[PDOK Adres Geocoding & Buurtdata](https://apify.com/codeclouds/pdok-locatieserver)** — Dutch address geocoding and neighbourhood demographics, useful if you need to map sector activity geographically.
- **[NL BESS Vergunningenmonitor](https://apify.com/codeclouds/nl-bess-vergunningenmonitor)** — same official publication family, applied to battery-storage (BESS) permit filings instead of CAO changes.

***

*Zoekwoorden: cao database, cao wijzigingen, collectieve arbeidsovereenkomst monitor, algemeenverbindendverklaring scraper, cao loonsverhoging tracker, labour agreement Netherlands, cao sector monitoring.*

### Keywords

cao, collective labour agreement, arbeidsvoorwaarden, avv, algemeenverbindendverklaring, hr, payroll, netherlands, wage tracker, labour law

### Changelog

#### 0.4.0

- Added a curated sector-alias table: `sectorKeyword` now automatically expands to known variants of the
  same sector (e.g. `"thuiszorg"` also matches `"VVT"`-titled publications), and extracted `caoNaam` values
  are normalized to the canonical spelling. No pricing change.
- Added `partijen`: best-effort recognition of known employer/employee organisation names (FNV, CNV
  Vakmensen, VNO-NCW, etc.) in the full text. No pricing change.
- Added `alleenSignalen` input: only deliver (and charge for) records with a detected change signal or known
  wage trend, filtering out routine base publications for clients who only care about changes. Lowers
  charges for those clients.
- Added `citaat`: a short quote from the full text around the first matched `onderwerpen` keyword, for audit
  trails without opening `urlHtml`. No pricing change.
- Added a free `RUN_SUMMARY` written to the key-value store at the end of each run, including the average
  wage-trend delta across sectors with a known trend this run. No pricing change.

#### 0.3.1 - Bugfix

- Fixed `incrementalMode` (including `loon-trend-signaal`) never seeing prior-run state: the actor was
  reading/writing to Apify's per-Run *default* key-value store, which is not shared between separate runs. Now
  uses a named, persistent key-value store. A new baseline will be captured on the next run.

#### 0.3.0

- Added `vorigLoonpercentage`/`loonTrendDelta` (incremental mode) — compares a sector's wage-increase
  percentage against the last-known percentage for that sector from a previous run. New `loon-trend-signaal`
  event ($0.01), only charged when both values are known.

#### 0.2.0

- Added `onderwerpen`, a curated keyword classification of the full text on subject (loon, pensioen,
  werktijden, verlof, scholing) — so a non-wage change is recognisable instead of looking like an empty
  result. No pricing change.

#### 2026-07-12 - Documentation update

- Added a "Related Actors" section cross-linking other actors by this developer.

#### 0.1.0 - Initial release

- Structured CAO avv-publication feed from the official Staatscourant SRU API, with recordType classification (nieuwe\_avv, wijziging, verlenging, intrekking, verzoek), sector-name extraction, and best-effort looptijd/wage-increase parsing.

# Actor input Schema

## `sectorKeyword` (type: `string`):

Free-text filter on the full-text index, e.g. "Bouw", "Metaal", "Horeca", "Zorg".

## `publishedAfter` (type: `string`):

Only publications from on/after this date (YYYY-MM-DD), e.g. "2026-01-01".

## `recordTypes` (type: `array`):

Which title-derived publication types to include.

## `maxResults` (type: `integer`):

Maximum number of CAO records to return (1-2000). Each result requires an extra full-text fetch, so keep this reasonable.

## `incrementalMode` (type: `boolean`):

Only fetch publications newer than the previous run, and flag sectors seen before as a change signal. Ideal for scheduled monitoring. Uses a shared, persistent key-value store snapshot ('nl-cao-wijzigingen-database-snapshot') that is also read and updated by scheduled monitoring runs, not just this one. On the very first call ever (no snapshot exists yet), there is no previous run date or seen-sectors list to compare against: it falls back to the 'publishedAfter' filter (or fetches without a date filter if that is also empty), and no publication is flagged as a change signal since nothing is known as 'seen before' yet.

## `alleenSignalen` (type: `boolean`):

Only deliver (and charge for) records where isWijzigingSignaal is true or loonTrendDelta is known. Reduces cao-record charges for clients who only care about detected changes/trends.

## Actor input object example

```json
{
  "publishedAfter": "2026-01-01",
  "recordTypes": [
    "nieuwe_avv",
    "wijziging",
    "verlenging",
    "intrekking",
    "verzoek"
  ],
  "maxResults": 100,
  "incrementalMode": false,
  "alleenSignalen": false
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "publishedAfter": "2026-01-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/nl-cao-wijzigingen-database").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "publishedAfter": "2026-01-01" }

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/nl-cao-wijzigingen-database").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "publishedAfter": "2026-01-01"
}' |
apify call codeclouds/nl-cao-wijzigingen-database --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=codeclouds/nl-cao-wijzigingen-database",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/NAtIzafkh6JTOBXmW/builds/y6UochNAvs5CEuBgQ/openapi.json
