# NL Traffic Congestion Index Monitor (NDW) (`codeclouds/nl-filedruk-index-monitor`) Actor

Check live Dutch traffic congestion (filedruk, verkeersdrukte) per province or road network in one call — real vs. free-flow travel time from NDW open data, pre-classified per province with optional alerts/trend, skipping raw DATEX II XML parsing. From $0.008/record, PPE.

- **URL**: https://apify.com/codeclouds/nl-filedruk-index-monitor.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** Other, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 filedruk-groep-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/actors/running/actors-in-store.md#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 Traffic Congestion Index Monitor (NDW) 🇳🇱🚗

Live **traffic congestion index per Dutch province**, computed from the official NDW (Nationale Databank
Wegverkeersgegevens) open-data travel-time feed. No configuration required, no API key, no scraping — this
actor fetches NDW's real-time DATEX II travel-time publication, compares actual measured travel time against
each measurement point's free-flow reference value, and aggregates the result into one congestion score per
province (plus separate national-highway and municipal-road summaries).

### Who is this for?

- **Logistics planners & fleet operators** — quick daily congestion signal per province, no need to parse raw NDW feeds yourself
- **Real-estate & site-selection analysts** — accessibility context for a region, alongside other location data
- **Media/data journalists** — a ready-made national congestion index for reporting
- **AI agents** — structured JSON output, ideal as a tool for agentic workflows (MCP-ready)

### When should an AI agent use this?

- "What's the current traffic congestion level in Zuid-Holland?"
- "Which Dutch province has the worst traffic right now?"
- "Give me a national traffic congestion snapshot."
- "Alert me when congestion in Noord-Brabant crosses a certain threshold."

### What this Actor does

- Fetches NDW's live `traveltime.xml.gz` feed (updated roughly every minute, DATEX II/XML format)
- For every measurement point, compares the actual/reconstituted travel time against its static free-flow
  reference value
- Classifies each measurement point to a province using the road authority's own site-ID naming convention
  (see **Methodology** below) — measurement points on national highways or municipal roads that can't be
  attributed to a specific province are reported as separate summary groups instead of being silently dropped
- Aggregates to one record per group: number of measurement points, average travel-time ratio, and a derived
  0-100+ congestion score
- Optional threshold alerting: flag groups whose congestion score crosses a value you set

### Methodology (please read before use)

NDW's own measurement-site IDs for provincial roads follow the pattern `P<province-code><number>_...`
(e.g. `PNB05_BRE_Keizerstraat_N01`). This is the road authority's own convention, not something this actor
invents. It was empirically verified on 2026-07-15 by resolving 11 measurement points (spanning 10 of the 12
province codes) to their real coordinates via NDW's own location table and reverse-geocoding each one with the
official PDOK Locatieserver — all 11 matched the expected province.

- **Known edge case:** a handful of specific road segments run right along a province border (e.g. a bridge/dam
  connecting two provinces). Such a segment is grouped under the province that administers it, which can
  occasionally differ from strict geographic containment.
- Measurement points on the national highway network (`RWS...`) generally don't carry a province code at
  all — these are reported as **"Rijkswegennet (niet provinciaal ingedeeld)"**, a separate group.
- Measurement points on municipal roads (`G...`) are reported as **"Gemeentelijk wegennet"**.
- Anything that doesn't match a known prefix is reported as **"Overig/onbekend wegbeheerder"** — nothing is
  ever silently excluded from the totals.

This actor does **not** perform per-point geocoding at runtime — that would require tens of thousands of
lookups per run and isn't practical. The province classification is a static, empirically spot-checked mapping
from the road authority's own ID prefix.

### Input

| Field | Type | Description |
|---|---|---|
| `provincies` | array of strings | Limit output to these exact province names (e.g. `"Noord-Holland"`). Empty (default) = all provinces plus the non-provincial groups. |
| `includeOngeclassificeerd` | boolean | Also return the national-highway and municipal-road summary groups (default `true`) |
| `minMeetpunten` | integer | Skip groups with fewer measurement points than this (default `5`) |
| `alertDrempel` | integer | Congestion-score threshold (0-100+) that triggers a paid high-congestion alert for a group. Leave empty to disable. |
| `trackTrend` | boolean | Compare each group's score against the previous run and flag rising/falling/unchanged. Default `false`. |
| `topNPerGroep` | integer | Include the N busiest individual measurement points per group (1-50). Leave empty to disable. |

### Output

One record per group (province, or non-provincial category):

```json
{
  "groep": "Zuid-Holland",
  "classificatieMethode": "provincie-wegbeheerder-prefix",
  "aantalMeetpunten": 2807,
  "gemiddeldeReistijdRatio": 1.253,
  "filedrukScore": 25,
  "hogeFiledrukAlert": true,
  "dagdeel": "ochtendspits",
  "publicatietijd": "2026-07-15T09:37:42.012Z",
  "trend": null,
  "bron": "NDW Open Data — traveltime.xml.gz (opendata.ndw.nu, DATEX II)"
}
```

- **`gemiddeldeReistijdRatio`**: average of (actual travel time / free-flow reference time) across the
  group's measurement points that have a reference value
- **`filedrukScore`**: `round((ratio - 1) * 100)`, floored at `0` — `0` means at-or-below free-flow speed on
  average, `25` means travel times are running ~25% above free-flow, etc.
- **`classificatieMethode`**: `"provincie-wegbeheerder-prefix"` for province groups, `"niet-provinciaal"` for
  the national-highway/municipal-road/unknown groups
- **`hogeFiledrukAlert`**: `null` when `alertDrempel` isn't set, otherwise `true`/`false`
- **`dagdeel`**: `"ochtendspits"`/`"avondspits"`/`"dal"`, derived from `publicatietijd` converted to
  Europe/Amsterdam local time (DST-aware). `null` if `publicatietijd` is unknown
- **`trend`**: `null` unless `trackTrend` is enabled; otherwise `"oplopend"`/`"dalend"`/`"gelijk"` compared to
  the previous run, or `null` on the very first run (no prior snapshot yet)

#### Trend between runs (`trackTrend`, optional, separately charged)

With `trackTrend: true`, the actor stores each group's `filedrukScore` in its key-value store after every run
and compares against it on the next one. Each record then gets a `trend` field: `"oplopend"` (higher than last
run), `"dalend"` (lower), or `"gelijk"` (same). The very first run (no stored snapshot yet, or a group that's
new) always gets `trend: null`. Charged as `trend-signaal`, only for groups actually flagged `"oplopend"` or
`"dalend"` — an unchanged group is never charged.

#### Top-N busiest measurement points (`topNPerGroep`, optional, separately charged)

With `topNPerGroep` set (e.g. `5`), each group record gets a `topMeetpunten` array with the N individual
measurement points that have the highest travel-time ratio within that group, sorted descending:

```json
{
  "groep": "Zuid-Holland",
  "topMeetpunten": [
    { "siteId": "PZH04_A20_Gouda_L01", "reistijdRatio": 2.31 },
    { "siteId": "PZH04_A12_Zoetermeer_L02", "reistijdRatio": 2.08 }
  ]
}
```

Charged as `meetpunt-detail`, once per measurement point actually included (e.g. `topNPerGroep: 5` on a run
with 10 groups charges up to 50 `meetpunt-detail` events, on top of the 10 `filedruk-groep-record` events).

#### National summary (free extra dataset record)

Whenever there's at least one group record in the run, one extra record with `"type":
"landelijke-samenvatting"` is pushed to the dataset (no extra charge) — the national average
`filedrukScore`, the busiest province (`drukstProvincie`/`drukstProvincieScore`), and the total count of
groups that triggered `hogeFiledrukAlert` this run:

```json
{
  "type": "landelijke-samenvatting",
  "aantalGroepen": 12,
  "gemiddeldeFiledrukScore": 18.4,
  "drukstProvincie": "Zuid-Holland",
  "drukstProvincieScore": 25,
  "aantalAlerts": 2,
  "dagdeel": "ochtendspits",
  "publicatietijd": "2026-07-15T09:37:42.012Z",
  "bron": "NDW Open Data — traveltime.xml.gz (opendata.ndw.nu, DATEX II)"
}
```

### Use cases

**Just the provinces, no national-highway/municipal noise:**

```json
{ "includeOngeclassificeerd": false }
```

**Watch specific provinces with an alert:**

```json
{ "provincies": ["Noord-Holland", "Zuid-Holland", "Utrecht"], "alertDrempel": 30 }
```

### Pricing

This Actor uses Apify's Pay-Per-Event (PPE) pricing model.

- **Actor Start:** $0.00005 (Apify default)
- **filedruk-groep-record:** $0.008 per group record delivered
- **hoge-filedruk-alert:** $0.02 extra, only when `alertDrempel` is set and a group's congestion score reaches it
- **trend-signaal:** $0.0125 extra, only with `trackTrend` enabled and a group actually flagged `"oplopend"` or `"dalend"`
- **meetpunt-detail:** $0.005 per measurement point, only with `topNPerGroep` set, per point actually included

### Legal

- Data source: **NDW (Nationale Databank Wegverkeersgegevens)**, `opendata.ndw.nu` — a joint open-data
  service of Rijkswaterstaat, provinces, and municipalities, explicitly published for reuse. No
  authentication required.
- Purely aggregated traffic-flow measurements on the road network — no license plates, no individual vehicle
  identification, no personal data.
- Province classification is a best-effort, empirically spot-checked heuristic based on the road authority's
  own ID convention (see **Methodology** above), not an officially published NDW mapping table.

### FAQ

**Q: How current is the data?**
A: NDW updates the travel-time feed roughly every minute. Each run reflects a live snapshot at the moment it
runs — schedule the actor periodically (e.g. hourly) to build a time series.

**Q: Why do some groups have far more measurement points than others?**
A: Coverage reflects NDW's actual sensor/camera/floating-car-data network, which is denser in the Randstad and
along major national highways. This isn't adjustable.

**Q: What if a province name I pass in `provincies` doesn't match anything?**
A: You'll get zero records for that run — province names must match exactly (e.g. `"Noord-Holland"`, not
`"Noord Holland"` or `"NH"`).

### Related Actors

- **[PDOK Locatieserver](https://apify.com/codeclouds/pdok-locatieserver)** — used during this actor's own
  development to verify the province-classification methodology above.

***

*Zoekwoorden: filedruk, verkeersdrukte, reistijdindex, NDW verkeersdata, files per provincie, verkeerscongestie
Nederland, filedruk monitor, actuele filedruk.*

### Keywords

traffic congestion, filedruk, ndw, travel time index, netherlands traffic, road congestion, datex ii, verkeersdata

### Changelog

#### 0.3.0 - Agent-readiness: task-oriented description

- Rewrote the Store description/short description (task-oriented first sentence, explicit NL/EN synonyms
  "filedruk"/"verkeersdrukte"/"congestion", a decision-benefit sentence, and a plain-text price indication)
  as part of the portfolio-wide agent-readiness pass. No code, schema, or pricing change — `.actor/actor.json`
  and `STOREINFO.md` are kept identical.

#### 0.2.0 - Trend detection & top-N busiest measurement points

- Added `trackTrend` — compares each group's `filedrukScore` against the previous run via a named
  key-value-store snapshot, flags `trend` as `"oplopend"`/`"dalend"`/`"gelijk"`. New `trend-signaal` event
  ($0.0125), only charged for groups actually flagged rising or falling.
- Added `topNPerGroep` — includes the N individual measurement points with the highest travel-time ratio per
  group (`topMeetpunten`). New `meetpunt-detail` event ($0.005 per point), only charged per point actually
  included.

#### 0.1.1

- Added `dagdeel` (ochtendspits/avondspits/dal) per record, derived from `publicatietijd` in NL-local time.
  No pricing change.
- Added a free `landelijke-samenvatting` dataset record per run (national average, busiest province, alert
  count). No pricing change.

#### 0.1.0

- Initial release: live NDW travel-time feed aggregated into a congestion index per province, with
  national-highway/municipal-road summary groups and optional threshold alerting.

# Actor input Schema

## `provincies` (type: `array`):

Limit output to these exact province names (e.g. "Noord-Holland"). Leave empty to return all provinces plus the non-provincial groups (national/municipal roads).

## `includeOngeclassificeerd` (type: `boolean`):

Also include separate summary records for measurement points that could not be classified to a province (national highway network and municipal road network), instead of silently dropping them.

## `minMeetpunten` (type: `integer`):

Groups with fewer measurement points than this are skipped (too little signal for a meaningful average).

## `alertDrempel` (type: `integer`):

When set, groups with a congestion score at or above this threshold trigger a paid 'hoge-filedruk-alert' event. Leave empty to disable.

## `trackTrend` (type: `boolean`):

Compares each group's congestion score against the previous run (via the actor's key-value store) and flags it as rising, falling, or unchanged. The first run (no prior snapshot) always returns null. New charged event trend-signaal ($0.0125), only when a group is actually flagged rising or falling.

## `topNPerGroep` (type: `integer`):

Includes the N individual measurement points with the highest travel-time ratio per group. New charged event meetpunt-detail ($0.005 per measurement point included). Leave empty to disable.

## Actor input object example

```json
{
  "provincies": [],
  "includeOngeclassificeerd": true,
  "minMeetpunten": 5,
  "trackTrend": false
}
```

# Actor output Schema

## `results` (type: `string`):

Alle filedruk-index-records in het default dataset.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/nl-filedruk-index-monitor").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 = {}

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

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

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

```

## CLI example

```bash
echo '{}' |
apify call codeclouds/nl-filedruk-index-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/nl-filedruk-index-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

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