# NL Parking Rates & Live Availability Monitor (`codeclouds/nl-parking-monitor`) Actor

Clean, structured parking data for the Netherlands: current tariffs and real-time free-space counts per facility, sourced from RDW's national Open Parkeerdata feed. Detects when a garage's live status changes between runs.

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

## Pricing

from $3.00 / 1,000 parking-results

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 Parking Rates & Live Availability Monitor 🅿️

Clean, structured **parking data for the Netherlands**: current tariffs and real-time free-space counts per facility, straight from RDW's national **Open Parkeerdata** feed (Nationaal Parkeer Register). Detects when a garage's live status changes between runs, so you can build alerts, not just dumps.

No login, no API key — this is the same open, government-published feed used by NL parking apps and navigation systems.

### Why this actor?

The raw source feed is genuinely painful to work with: every tariff rule is repeated **once per weekday** (a simple hourly rate becomes 7 near-identical records), timestamps are Unix epoch seconds, capacity is sometimes only available in a completely separate live-status endpoint, and addresses are buried several levels deep. This actor does that cleanup for you:

- 🧹 **Tariff simplification** — the 7x weekday-duplicated raw entries are collapsed into one rule per unique price structure, with a computed €/hour rate and a `free` / `metered` / `daily_cap` classification
- 🟢 **Live availability, normalized** — free spaces, occupancy %, and a plain `available` / `nearly_full` / `full` / `closed` status, not raw booleans you have to interpret
- 🔔 **Change detection** — remembers each facility's status from the previous run and flags `status_changed` when it flips (e.g. `available` → `full`), so scheduled monitoring runs give you alerts, not noise
- 📍 **Resolved address & coordinates** — pulled from the facility's actual entrance, not the operator's town-hall mailing address
- 🛡️ **Stale-data awareness** — expired tariff periods are dropped rather than reported as current, and `staticDataLastUpdated` tells you how fresh each record is

Only about 526 of the ~14,870 facilities in the feed publish live occupancy; the rest have tariffs only (mostly street parking and P+R zones). The actor tells you which is which per record (`hasRealtimeData`).

### When should an AI agent use this?

- "How much does it cost to park at parkeergarage Forum in Groningen right now, and are there free spaces?"
- "Is there still availability in a parking garage in Rotterdam I can drive to now?"
- "Alert me when a specific garage's status flips from available to full."
- "Compare the hourly parking rate across garages in a city before I decide where to park."
- "Which parking facilities in this feed actually report live occupancy, and which only have tariff info?"
- "Find the facility ID for a garage by name so I can monitor it in future runs."

### What data do you get?

```json
{
  "identifier": "9f89a6c7-df06-45ea-adf6-0b94088fcb57",
  "name": "parkeergarage Forum",
  "operatorName": "Groningen",
  "address": { "street": "Schoolstraat", "houseNumber": "16", "zipcode": "9712JS", "city": "Groningen", "province": "" },
  "location": { "lat": 53.219207, "lon": 6.57081 },
  "usage": "Garage parkeren",
  "capacity": 325,
  "evLaadpuntenCapaciteit": 10,
  "tariffs": [
    {
      "description": "Garagetarief (1,00 euro/18 min)",
      "kind": "metered",
      "days": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
      "validFrom": "00:00", "validUntil": "23:59",
      "rates": [{ "chargeEuro": 1, "chargePeriodMinutes": 18, "pricePerHourEuro": 3.33, "durationFromMinutes": 0, "durationUntilMinutes": null }]
    },
    {
      "description": "Garagetarief (24,00 euro/1440 min)",
      "kind": "daily_cap",
      "days": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
      "validFrom": "00:00", "validUntil": "23:59",
      "rates": [{ "chargeEuro": 24, "chargePeriodMinutes": 1440, "pricePerHourEuro": 1, "durationFromMinutes": 0, "durationUntilMinutes": null }]
    }
  ],
  "hasRealtimeData": true,
  "realtime": { "open": true, "full": false, "capacity": 325, "vacantSpaces": 67, "occupancyPercent": 79.4, "status": "available", "lastUpdated": "2026-07-09T14:17:44.000Z" },
  "changeType": "unchanged",
  "previousStatus": "available",
  "sourceUrls": { "static": "https://npropendata.rdw.nl/...", "dynamic": "https://npropendata.rdw.nl/..." }
}
```

### Use cases

- **Parking-finder apps & dashboards** — one clean call per facility gets you tariff and live availability together, ready to render
- **"Notify me when it's full" alerts** — schedule runs with `detectChanges` on and route `status_changed` records to Slack/e-mail via Apify integrations
- **Price comparison** — compare `pricePerHourEuro` across garages in a city before you drive there
- **AI agents & MCP tools** — flat, predictable JSON with a small, well-described input schema; a single call returns everything needed to answer "is there parking near X, and what does it cost right now?" For a one-off lookup like this, consider setting `detectChanges: false` — it defaults to `true` for scheduled monitoring use, and every call (ad-hoc or scheduled) reads and overwrites the same persistent snapshot. If you also run scheduled monitoring on the same facilities, an ad-hoc call in between updates that shared baseline and can cause the next scheduled run to miss or misattribute a real change.
- **Facility discovery** — leave `query`/`facilityIds` empty to browse a sample of facilities with live data, then narrow down by name

### Quick start

**Zero-config**: returns the first 25 facilities that publish live occupancy, no input needed.

**Search by name:**

```json
{ "query": "Groningen", "maxResults": 50 }
```

**Monitor specific garages and alert on status changes:**

```json
{
  "facilityIds": ["9f89a6c7-df06-45ea-adf6-0b94088fcb57", "76825e73-4c8d-4a9c-9061-6284afcd865c"],
  "detectChanges": true
}
```

Schedule this hourly: the first run establishes a baseline, every later run only marks `status_changed` when a garage actually flips between available/full/closed.

**Only currently-available garages right now:**

```json
{ "query": "Rotterdam", "onlyAvailableNow": true, "maxResults": 100 }
```

### Input reference

| Field | Description |
|---|---|
| `facilityIds` | Exact RDW facility UUIDs to fetch (skips name search) |
| `query` | Case-insensitive name search, e.g. a city or garage name |
| `fuzzyMatch` | Opt-in typo-tolerant matching for `query`: matches by edit distance instead of requiring an exact substring, and ignores word order (default `false`) |
| `nearLatitude`/`nearLongitude`/`radiusKm` | Only keep facilities within `radiusKm` (default `5`) of this point. Both coordinates must be set. Useful for street-parking zones with no city/place name to search by |
| `onlyWithRealtimeData` | Only facilities with live occupancy (default `true`) |
| `onlyAvailableNow` | Only open, non-full facilities (default `false`) |
| `maxResults` | Cap on facilities fetched/checked per run (default `25`, max `2000`) |
| `detectChanges` | Compare against the previous run and flag status changes (default `true`). Consider `false` for one-off/ad-hoc lookups (e.g. MCP tool calls) if you also run scheduled monitoring on the same facilities — every call shares and overwrites the same persistent snapshot, so an ad-hoc call in between can affect what the next scheduled run reports. |
| `trackOccupancyHistory` | Build up an occupancy time series per facility across scheduled runs (default `false`) |
| `maxHistoryPoints` | How many past measurements to keep per facility (default `30`) |
| `historyRetentionDays` | How many days of inactivity before a facility's occupancy history is pruned from the key-value store (default `90`), preventing unbounded growth from facilities no longer requested |
| `voorspelBeschikbaarheid` | Compare current occupancy against the historical average at the same hour of day (default `false`). Only meaningful together with `trackOccupancyHistory` |

Note: `maxResults` bounds how many facilities are *fetched*, before filters like `onlyAvailableNow` are applied — so the final output can be smaller than the cap. This keeps run cost predictable instead of crawling the full ~14,870-facility index looking for matches.

### Pricing

Pay per event: regular facility records are billed at the base `parking-result` rate; a detected **`availability-change-alert`** (a facility's live status actually changed since the last run) is billed at a higher rate — you pay more for genuine insight than for a plain snapshot. When `trackOccupancyHistory` is enabled, each facility that gets a new history point is additionally billed at the **`occupancy-history-point`** rate ($0.005) — this only applies to facilities with real-time data. When `voorspelBeschikbaarheid` is also enabled, each facility with a computable busier/quieter-than-usual comparison is additionally billed at the **`drukte-voorspelling`** rate ($0.01) — only when a comparable prior measurement (same hour of day) actually exists.

### Data source & reliability

- Source: RDW's **Open Parkeerdata** (Nationaal Parkeer Register), the Dutch government's public parking-data standard (SPDP2.0) — [data.overheid.nl](https://data.overheid.nl), CC-BY licensed
- No authentication required; this actor respects the source's rate limiting with automatic backoff and retry
- One broken facility lookup never fails the whole run — errors are collected per facility in the run's key-value store (`RUN_SUMMARY`)
- Facility metadata (name, operator, address) is business/government data — no personal data is collected

### FAQ

**Why doesn't every facility have live availability?** Only municipalities/operators that have installed occupancy sensors publish a `dynamicDataUrl`. About 526 of ~14,870 facilities currently do — mostly larger garages in bigger cities. Facilities without it still return full tariff information.

**Why is a facility's tariff list empty?** Its published validity period has expired in the source data. Rather than show stale pricing, the actor drops it — check `staticDataLastUpdated` for freshness.

**How do I find a facility's ID?** Run once with a `query` (e.g. a city name) and no `facilityIds` — the returned `identifier` field is reusable in future runs.

**Where is the change-detection history stored?** A compact per-facility status snapshot lives in the actor's key-value store; each run compares against it and updates it.

**What does `bezettingsGeschiedenis` contain?** When `trackOccupancyHistory` is enabled, each facility record gets an array of past `{timestamp, occupancyPercent, vacantSpaces}` points (oldest first, capped at `maxHistoryPoints`), built up across scheduled runs via the actor's key-value store. Only facilities with real-time data get history points.

**What does `drukteVoorspelling` contain?** When `voorspelBeschikbaarheid` is enabled, each facility record gets a `{gemiddeldeOccupancyPercent, aantalVergelijkbareMetingen, verschilPercentagePunten, richting}` object comparing the current occupancy against the historical average at the *same hour of day* (built from `bezettingsGeschiedenis`) — a "busier/quieter than usual for this time" signal rather than a comparison against the all-time average. `richting` is `"drukker"`/`"rustiger"` when the difference exceeds a small noise-margin (5 percentage points), otherwise `"gelijk"`. Stays `null` until there's at least one prior measurement at the same hour to compare against — the very first run, or a facility only ever checked at different times of day, always gets `null`.

### Related Actors

Also by this developer:

- **[NL Waterstanden & Watertemperatuur Monitor](../nl-waterstanden-monitor)** — same profile: a real-time Dutch open-government-data monitoring feed, no login/API key required.
- **[PDOK Adres Geocoding & Buurtdata](https://apify.com/codeclouds/pdok-locatieserver)** — geocode parking facility addresses to get coordinates and neighbourhood context.
- **[NL Milieuzones & Zero-Emissiezones Monitor](https://apify.com/codeclouds/nl-emissiezones-monitor)** — same profile: a direct, authentication-free NDW road-traffic-data feed, no scraping involved.
- **[NL Ziekenhuis Wachttijden Monitor](https://apify.com/codeclouds/nl-ziekenhuis-wachttijden-monitor)** — same profile: a direct, authentication-free Dutch government REST feed, no scraping involved.

***

*Keywords: nederland parkeren, parkeertarieven, parkeergarage beschikbaarheid, real-time parking availability netherlands, RDW open data, nationaal parkeer register, parking API netherlands, MCP parking tool.*

### Keywords

netherlands, parking, parking-rates, parking-availability, open-data, rdw, real-time, mcp-tool

### Changelog

#### 0.5.0 - Busier/quieter-than-usual prediction

- Added `voorspelBeschikbaarheid` (opt-in, requires `trackOccupancyHistory`) — compares current occupancy
  against the historical average at the same hour of day, exposed as `drukteVoorspelling`. New
  `drukte-voorspelling` event ($0.01), only charged when a comparable prior measurement (same hour) exists.

#### 0.4.0

- Added `nearLatitude`/`nearLongitude`/`radiusKm` for geographic radius search, using each facility's
  resolved location. Solves street-parking zones with no city/place name in `query` to search by. No pricing
  change — same `parking-facility-record` event.
- Added `fuzzyMatch`: opt-in typo-tolerant `query` matching (edit distance + order-independent tokens),
  for abbreviated/misspelled names or different word order. No pricing change.
- Fixed unbounded growth of the occupancy-history key-value store: added `historyRetentionDays` (default
  90\) — facilities with no new measurement for that long are pruned automatically. No pricing change.

#### 0.3.2 - Bugfix

- The change-detection snapshot is now merged with the previous run's snapshot instead of being fully
  replaced, matching the occupancy-history store's existing merge behaviour. Previously, running the actor
  with a different `query`/`facilityIds` between scheduled runs could wipe the tracked status of facilities
  left out of that run, causing a later run to miss a real `availability-change-alert`.

#### 0.3.1 - Bugfix

- Fixed `status_changed` detection and `trackOccupancyHistory` never seeing prior-run state: the actor was
  reading/writing its snapshot/history 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 `trackOccupancyHistory` (opt-in) — builds up an occupancy time series per facility across scheduled
  runs, exposed as `bezettingsGeschiedenis`. New `occupancy-history-point` event ($0.005), only charged for
  facilities with real-time data that got a new point this run.

#### 0.2.0

- Added `evLaadpuntenCapaciteit` — EV-charging-point capacity, extracted separately from the general
  `capacity` field (which some facilities' static feed conflates with EV capacity). `null` when no
  specification has a `chargingPointCapacity` value; `0` is a real "no EV charging points" value, not an
  absence. No pricing change.

#### 0.1.0 - Initial release

- Tariff simplification (weekday-duplicated rules collapsed into unique price structures with computed €/hour rate)
- Live availability normalization (`available` / `nearly_full` / `full` / `closed`)
- Change detection between runs (`status_changed` alerts via Pay-Per-Event)
- Facility search by name (`query`) or exact `facilityIds`

# Actor input Schema

## `facilityIds` (type: `array`):

Exact RDW facility identifiers (UUIDs) to fetch, e.g. \["c496f775-0b16-4f5e-8f7a-1a2b3c4d5e6f"]. Skips the name search entirely. Leave empty to search by name/query instead.

## `query` (type: `string`):

Case-insensitive text match against the facility name, e.g. "Groningen" or "Forum". Ignored if Facility IDs is set. Leave empty to browse all facilities.

## `onlyWithRealtimeData` (type: `boolean`):

Only include facilities that publish real-time free-space counts (about 526 of the ~14,870 in the feed). Turn off to also include tariff-only facilities such as street parking and P+R.

## `onlyAvailableNow` (type: `boolean`):

Only keep facilities that are open right now and not full. Requires live occupancy data; facilities without it are excluded when this is on.

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

Caps how many facilities are fetched and checked in this run (1-2000). Filters like "Only currently available" are applied after fetching, so the final output can be smaller than this number.

## `detectChanges` (type: `boolean`):

Compare each facility's live status against the previous run and flag ones that changed (e.g. available -> full). Charged at the higher "availability-change-alert" rate; unchanged/first-seen facilities are charged the standard rate.

## `trackOccupancyHistory` (type: `boolean`):

Build up an occupancy time series per facility across scheduled runs (stored in the actor's key-value store), capped at maxHistoryPoints. Only applies to facilities with real-time data. Charges a new occupancy-history-point event per facility that gets a new point.

## `maxHistoryPoints` (type: `integer`):

How many past measurements to keep per facility when trackOccupancyHistory is enabled. Oldest points are dropped once this cap is reached.

## `historyRetentionDays` (type: `integer`):

How many days of inactivity (no new measurement) before a facility's occupancy history is pruned from the key-value store. Prevents unbounded growth from facilities that are no longer requested.

## `nearLatitude` (type: `number`):

Only keep facilities within radiusKm of this latitude/longitude. Both nearLatitude and nearLongitude must be set for the filter to apply. Useful for street-parking zones that have no city/place name to search by.

## `nearLongitude` (type: `number`):

Only keep facilities within radiusKm of this latitude/longitude.

## `radiusKm` (type: `number`):

Radius in kilometers around nearLatitude/nearLongitude. Only applies when both coordinates are set.

## `fuzzyMatch` (type: `boolean`):

Opt-in typo-tolerant matching for the query field: matches query tokens against facility name tokens by edit distance instead of requiring an exact substring. Helps when the name is abbreviated, misspelled, or word order differs.

## `voorspelBeschikbaarheid` (type: `boolean`):

Compares current occupancy against the historical average at the same hour of day, built from trackOccupancyHistory's time series. Only meaningful together with trackOccupancyHistory -- without prior history the field stays null. New charged event drukte-voorspelling ($0.01), only when a comparable prior measurement actually exists.

## Actor input object example

```json
{
  "facilityIds": [],
  "onlyWithRealtimeData": true,
  "onlyAvailableNow": false,
  "maxResults": 25,
  "detectChanges": true,
  "trackOccupancyHistory": false,
  "maxHistoryPoints": 30,
  "historyRetentionDays": 90,
  "radiusKm": 5,
  "fuzzyMatch": false,
  "voorspelBeschikbaarheid": false
}
```

# Actor output Schema

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

Alle parkeergegevens 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-parking-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-parking-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-parking-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/nl-parking-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/jKqjpBxaxWb25VQCF/builds/YRmgnwYJ2e9IsTJii/openapi.json
