# EV Charging Stations Scraper - NL, DE, FR Prices & Status (`tiger19935/ev-charging-stations-scraper`) Actor

Official AFIR open data on EU EV charging stations: charge point (EVSE) IDs, connectors, kW, live availability and ad-hoc EUR/kWh prices for the Netherlands, plus Germany's and France's national registers. Radius search, change monitoring and LLM-ready output.

- **URL**: https://apify.com/tiger19935/ev-charging-stations-scraper.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 1 total users, 1 monthly users, 84.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 charging stations

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EV Charging Stations Scraper - EU Prices & Live Status

Get **every public EV charging station in the Netherlands, Germany and France** from the official open-data registers - with **charge point (EVSE) IDs, connectors, kW, live availability and ad-hoc €/kWh prices** - as one clean dataset. No API keys, no scraping of apps, no blocked requests: the data comes from the national access points that EU regulation **AFIR (Regulation (EU) 2023/1804)** requires charge point operators to feed.

- 🇳🇱 **Netherlands** - NDW DOT-NL (Dutch AFIR National Access Point): ~75,000 locations, **live status** of every charge point and **machine-readable ad-hoc prices** (€/kWh incl. VAT, per minute, session fee).
- 🇩🇪 **Germany** - Bundesnetzagentur Ladesäulenregister (official national register, updated monthly).
- 🇫🇷 **France** - Base nationale des IRVE (official consolidated dataset on data.gouv.fr, updated daily), incl. the operator's price text.

### What can you do with it?

- **Price intelligence for EV charging (CPOs, eMSPs, fleets, analysts)** - compare ad-hoc €/kWh of Fastned, Shell Recharge, Vattenfall InCharge, Allego, Tesla, Ionity... city by city.
- **Route and fleet planning** - find HPC (≥150 kW) CCS chargers around a depot or along a corridor with a radius search, sorted by distance.
- **Monitoring** - schedule it daily in *Changes* mode and get only stations that are **new, removed, re-priced or re-equipped** since the last run (perfect for Slack/Make/Zapier alerts).
- **Market research and site selection** - counts of charge points by operator, power class and postal code.
- **AI agents and RAG** - every station has a ready-made `llmSummary` text, and the whole run is saved as one Markdown document (`RESULTS_MD`).

### What data do you get?

| Field | Example | Notes |
|---|---|---|
| `stationId`, `evseIds[]` | `NL*NUO*EXCHA*0000016*1` | Official EVSE IDs (eMI3 format) where published |
| `operatorName`, `operatorId` | Vattenfall InCharge, `NL*NUO` | |
| `address`, `postalCode`, `city`, `lat`, `lng` | De Corridor 1, Breukelen | |
| `maxPowerKw`, `hasDcFast`, `connectorTypes[]` | 120, true, `["CCS"]` | Per charge point in `evses[].connectors[]` |
| `availability` | 1 available, 1 occupied | **Live** (NL), with timestamp |
| `adHocPrice.perKwh` / `perMinute` / `sessionFee` | 0.7744 €/kWh incl. 21 % VAT | Structured (NL) or the operator's price text (FR) |
| `priceRange` | min/max €/kWh across the station's charge points | |
| `paymentMethods`, `openingHours`, `is24x7`, `parkingType` | app, rfid, 24/7 | |
| `changeType`, `changedFields[]` | `updated`, `["price"]` | In *Changes* mode |
| `llmSummary` | "EV charging station - De Corridor 1, ..." | Plain-English text for AI |
| `sourceName`, `sourceLicense`, `sourceUpdatedAt` | | Provenance of every record |

Two output formats: **nested** (one item per station with an `evses[]` array - best for apps and AI) or **flat** (one row per station - best for Excel / Google Sheets / CSV).

### How to use it

1. Pick **countries** (Netherlands is the richest source: live status + prices).
2. Optionally narrow down: **radius search** (latitude, longitude, km), **cities**, **postal code prefixes**, **operators**, **minimum power** (50 = DC fast, 150 = HPC), **connector types**, *only available now*, *only with a price*.
3. Set **Max items** (this also caps your cost) and click **Start**.
4. Download the results as JSON, CSV, Excel, XML or HTML, or read them via API.

#### Example input - fast chargers around Amsterdam

```json
{
  "countries": ["NL"],
  "latitude": 52.3676,
  "longitude": 4.9041,
  "radiusKm": 15,
  "minPowerKw": 150,
  "connectorTypes": ["CCS"],
  "onlyWithPrice": true,
  "maxItems": 200
}
```

#### Example output (one station, shortened)

```json
{
  "stationId": "NL:NDW:114817b0b0bc15b09028d4f020d87e86",
  "country": "NL",
  "operatorName": "Vattenfall InCharge",
  "operatorId": "NL*NUO",
  "address": "De Corridor 1",
  "postalCode": "3621 ZA",
  "city": "Breukelen",
  "lat": 52.171461,
  "lng": 4.989638,
  "evseCount": 2,
  "evseIds": ["NL*NUO*EXCHA*0000016*1", "NL*NUO*EXCHA*0000016*2"],
  "connectorTypes": ["CCS"],
  "maxPowerKw": 120,
  "hasDcFast": true,
  "availability": { "available": 1, "occupied": 1, "outOfOrder": 0, "unknown": 0, "total": 2, "updatedAt": "2026-10-02T14:13:53Z" },
  "adHocPrice": { "perKwh": 0.7744, "perKwhExclVat": 0.64, "perMinute": null, "sessionFee": null, "currency": "EUR", "vatPercent": 21, "priceText": "Per kWh: 0.77 EUR", "priceQuality": "structured" },
  "paymentMethods": ["app", "rfid"],
  "openingHours": "24/7",
  "evses": [{ "evseId": "NL*NUO*EXCHA*0000016*1", "status": "AVAILABLE", "connectors": [{ "type": "CCS", "powerKw": 120, "currentType": "DC", "format": "CABLE" }] }],
  "sourceName": "NDW DOT-NL (Dutch AFIR National Access Point)",
  "llmSummary": "EV charging station - De Corridor 1, 3621 ZA Breukelen, NL. Operator: Vattenfall InCharge (NL*NUO). 2 charge points, max 120 kW, connectors: CCS. Live status: 1 available, 1 occupied, 0 out of order. Ad-hoc price: €0.77/kWh incl. VAT. ..."
}
```

Besides the dataset, every run stores:

- `OUTPUT` - run summary: status per source, data timestamps, counts of new/updated/removed stations, warnings;
- `RESULTS_MD` - all station summaries as one Markdown document for AI agents.

### Monitoring mode (Changes)

Set **Mode = Changes** and schedule the Actor (e.g. every morning). Each run returns only stations that are:

- `new` - appeared in the register,
- `updated` - changed price, equipment/location data or (optionally) live availability, with the list of `changedFields`,
- `removed` - disappeared from the register.

Choose what counts as a change in **What counts as a change** (`price`, `static`, `availability`). The first run stores a baseline. The saved state is kept per filter set (or per **Monitoring state key**), so you can run several independent monitors.

### Pricing

This Actor uses **pay per event** - you pay only for results:

| Event | Price |
|---|---|
| Station in snapshot mode | **$0.002** per station ($2 per 1,000) |
| Change in monitoring mode | **$0.001** per change ($1 per 1,000) |

plus a small per-run start fee shown on the *Pricing* tab. Platform usage is included. **Max items** and the *maximum cost per run* you set are always respected - the Actor stops before exceeding them.

*Example:* 500 fast-charging stations with prices = $1.00. A daily change monitor only pays for the stations that actually changed, typically a few cents per day.

### Integrations and API

Use the results anywhere: **Google Sheets, Excel, Make, Zapier, n8n, Slack, Airbyte**, webhooks, or directly via the **Apify API** and the **Apify MCP server** (so AI agents such as Claude or ChatGPT can call it as a tool).

```bash
curl -X POST "https://api.apify.com/v2/acts/<USERNAME>~ev-charging-stations-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"countries":["NL"],"cities":["Utrecht"],"minPowerKw":50,"maxItems":100}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("<USERNAME>/ev-charging-stations-scraper").call(run_input={"countries": ["NL"], "operators": ["Fastned"], "maxItems": 500})
for station in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(station["city"], station["maxPowerKw"], station["adHocPrice"])
```

### FAQ

**Is this legal?** Yes. The Actor reads official open-data files that public authorities publish for re-use (AFIR obliges operators to make this data available through national access points). It does not log in anywhere, does not bypass any protection and contains no personal data. Each record carries `sourceName` and `sourceLicense`: France is *Licence Ouverte 2.0*, Germany's register is published by the Bundesnetzagentur for re-use (attribution required), the Dutch data is NDW open data - please check the terms of each source for your use case.

**How fresh is the data?** Every run downloads the current files - nothing is cached. Netherlands: NDW updates status and tariffs continuously during the day; each record shows `sourceUpdatedAt` and `availability.updatedAt`. Germany: monthly register. France: daily consolidation.

**Why are there no live status / prices for Germany and France?** These registers do not publish them as open data yet. As more AFIR national access points open their dynamic feeds, they will be added - tell us which country you need in the *Issues* tab.

**What is an "ad-hoc price"?** The price for spontaneous charging without a subscription (card/app), which AFIR requires operators to publish. Subscription (eMSP) prices differ.

**Can I get all of Europe?** Currently NL, DE and FR - the three largest public charging networks in the EU. More countries are on the roadmap.

**How much memory does a run need?** The default 2 GB handles everything, including a full export of all three countries (about 185,000 stations in one run). Runs with filters (a city, a radius, a minimum power) also work with 1 GB.

**Something is wrong or missing?** Open an issue in the *Issues* tab - feature requests (new countries, fields, formats) are welcome.

### More tools by the same author

- [OCPP CSMS Smoke Test](https://apify.com/tiger19935/ocpp-csms-smoke-test): Virtual OCPP 1.6J charge point for testing your CSMS

# Changelog

This Actor's version history is a separate document: https://apify.com/tiger19935/ev-charging-stations-scraper/changelog.md

# Actor input Schema

## `countries` (type: `array`):

Official open-data source per country. NL = NDW DOT-NL (AFIR National Access Point: live status + machine-readable ad-hoc prices, ~75,000 locations). DE = Bundesnetzagentur Ladesäulenregister (static register, updated monthly). FR = Base nationale des IRVE (static, daily; prices as published free text).

## `latitude` (type: `number`):

Centre of a radius search, e.g. 52.3676 for Amsterdam. Use together with longitude and radius. Results are then sorted by distance.

## `longitude` (type: `number`):

Centre of a radius search, e.g. 4.9041 for Amsterdam.

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

Search radius around latitude/longitude in kilometres.

## `cities` (type: `array`):

Keep stations in these cities (case- and accent-insensitive, prefix match: "Amsterdam" also matches "Amsterdam-Zuidoost").

## `postalCodePrefixes` (type: `array`):

Keep stations whose postal code starts with any of these, e.g. "10" or "3581".

## `operators` (type: `array`):

Keep stations of these operators (substring match on operator or station name), e.g. "Fastned", "Ionity", "Tesla", "EnBW".

## `minPowerKw` (type: `integer`):

Keep stations with at least one connector of this power or more. 50 = DC fast, 150 = HPC (high power charging).

## `connectorTypes` (type: `array`):

Keep stations offering at least one of these connectors.

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

Uses live AFIR status. Applies to the Netherlands only (Germany and France publish no live status).

## `onlyWithPrice` (type: `boolean`):

Drop stations without a published ad-hoc (pay-as-you-go) price.

## `includePrices` (type: `boolean`):

Netherlands: joins the official OCPI tariffs (price per kWh incl. VAT, per minute, session fee). Turn off for a slightly faster run.

## `mode` (type: `string`):

Snapshot = all matching stations. Changes = only stations that are new, changed or removed since the previous run with the same filters (ideal for scheduled monitoring). The first run in Changes mode saves a baseline and outputs stations as "new" (up to Max items).

## `changeTriggers` (type: `array`):

Changes mode only. price = ad-hoc tariff changed; static = location, connectors, power, opening hours or payment changed; availability = live status changed (very frequent in NL, enable only for occupancy tracking).

## `stateKey` (type: `string`):

Optional name of the saved state for Changes mode (letters, digits, "-"). By default the key is derived from your filters, so each saved task keeps its own history.

## `outputFormat` (type: `string`):

Nested = one item per station with an evses\[] array (best for apps and AI agents). Flat = one row per station without nested arrays (best for Excel/CSV/Google Sheets).

## `includeLlmSummary` (type: `boolean`):

Adds llmSummary - one plain-English sentence block per station (location, power, live status, price) for AI agents and RAG. A Markdown digest of all results is also saved as RESULTS_MD in the key-value store.

## `maxItems` (type: `integer`):

Maximum number of dataset items (stations or changes) to output in this run. You pay per output item, so this also caps the cost.

## Actor input object example

```json
{
  "countries": [
    "NL"
  ],
  "cities": [],
  "postalCodePrefixes": [],
  "operators": [],
  "minPowerKw": 150,
  "connectorTypes": [],
  "onlyAvailableNow": false,
  "onlyWithPrice": false,
  "includePrices": true,
  "mode": "snapshot",
  "changeTriggers": [
    "price",
    "static"
  ],
  "outputFormat": "nested",
  "includeLlmSummary": true,
  "maxItems": 100
}
```

# Actor output Schema

## `stations` (type: `string`):

Charging stations or changes found in this run.

## `summary` (type: `string`):

Per-source status, data timestamps, counts of new/updated/removed stations and warnings.

## `markdown` (type: `string`):

All llmSummary lines of this run as one Markdown document.

# 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 = {
    "countries": [
        "NL"
    ],
    "minPowerKw": 150,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/ev-charging-stations-scraper").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 = {
    "countries": ["NL"],
    "minPowerKw": 150,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/ev-charging-stations-scraper").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 '{
  "countries": [
    "NL"
  ],
  "minPowerKw": 150,
  "maxItems": 100
}' |
apify call tiger19935/ev-charging-stations-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/ev-charging-stations-scraper"
        }
    }
}
```

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/u3GoOHj6fcwOLiJ4k/builds/fvl21LbMIzfSVFvCB/openapi.json
