# EU EV Charging Graph - DE + FR + ES charge points by EVSE ID (`rastriq/eu-ev-charging-graph`) Actor

Homogeneous inventory of public EV charge points from the official registers of Germany, France and Spain, keyed by EVSE ID, with operator normalisation, quality flags and a change history (new / changed / removed) the sources do not keep.

- **URL**: https://apify.com/rastriq/eu-ev-charging-graph.md
- **Developed by:** [Rastriq — Structured data from the world](https://apify.com/rastriq) (community)
- **Categories:** Business, Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 charge point returneds

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

## EU EV Charging Graph – one EVSE-level inventory of EV charge points in Germany, France and Spain

**EU EV Charging Graph** merges the three official national registers of public EV charge points – Germany (Bundesnetzagentur), France (IRVE, data.gouv.fr) and Spain (DGT National Access Point) – into **one flat table keyed by EVSE ID**, and keeps the **history the sources do not keep**: which charge points appeared, changed or disappeared since your last run.

This is **EV charging station data for Germany, France and Spain** in one schema. Instead of writing three parsers (a wide German CSV with up to six points per row, a 159 MB French CSV and a DATEX II XML feed), you get the same columns for every country: normalised EVSE ID, operator, location, power, connector types, opening hours, payment methods, quality flags and change tracking.

> Rastriq – data infrastructure for official registers. Not affiliated with the Bundesnetzagentur, data.gouv.fr or the DGT.

### Use cases: what can you do with EV charging station data for Germany, France and Spain?

**1. Benchmark charge point operators (CPOs)** - *analysts, consultancies and CPO strategy teams.*
Compare operators by network size and power mix, per country or across all three. Group by `operator_name_norm` (it merges legal forms and country variants, so `Tesla Germany GmbH` = `TESLA France SARL` = `tesla`) and `operator_id` (the 5-character eMI3 prefix from the EVSE ID). Count `evse_id` rows and split by `power_kw` and `connector_type`. You get a league table of who operates how many fast chargers where.

**2. Site selection for retail, real estate and fleets** - *retail chains, property developers and fleet managers.*
Find where chargers already exist before you add or skip them. Filter by `postalCodes` and `minPowerKw`, then use `lat`, `lon`, `power_kw`, `opening_hours` and `payment_methods` to map the competition around a plot or depot. You get a ranked list of areas with good or poor coverage.

**3. Monitor high-power (HPC) roll-out** - *infrastructure funds, OEM charging teams and trade press.*
Run `delta` weekly with `minPowerKw: 150` and read the `new` rows: `operator_name_norm`, `city`, `power_kw`, `commissioning_date`. `removed` rows show decommissioned points. You get a weekly count of new fast-charging points by operator and region (recipe below).

**4. Investors and infrastructure due diligence** - *investors, lenders and M\&A teams.*
Check what a target company really owns on the ground: filter `operators` by name or EVSE prefix (e.g. `DEENB`), then count points, power classes and `commissioning_date`, and use `quality_flags` to see how clean its published data is. You get an independent, register-based view of a network to compare with the figures in the data room.

**5. Roaming and EVSE ID matching between platforms** - *e-mobility service providers, roaming hubs and platform teams.*
Reconcile your own charge point list with the official registers on the normalised `evse_id` (`DE*ABC*E123` becomes `DEABCE123`), with `evse_id_raw` for the original. Rows with `missing_evse_id`, `invalid_evse_format` or `duplicate_evse` are flagged, not hidden, so you can see exactly why an ID does not match. You get a match rate and a fix list per operator.

**6. Data quality for maps and apps** - *navigation, map and charging-app providers.*
Compare your charger data with the registers: look for points you miss, points that no longer exist (`removed`), wrong power (`changed_fields: ["power_kw"]` with `previous_values`) and coordinates outside the country (`coords_out_of_country`). You get a prioritised list of corrections.

**7. Coverage analysis by postal code** - *municipalities, mobility consultants and energy utilities.*
Use `postalCodes` (full codes or prefixes such as `28` for Madrid province) with `includeConnectors: false` for one row per station and `evse_count`. You get chargers per postal area, to compare with population or vehicle registrations.

#### Recipe 1: weekly roll-out monitor for 150 kW+ chargers

The first run is a full `baseline` of fast chargers in the three countries; each later run returns only new, changed and removed points. Excluding rows without an EVSE ID and extra copies of repeated IDs keeps the data clean.

```json
{
  "mode": "delta",
  "countries": ["DE", "FR", "ES"],
  "minPowerKw": 150,
  "excludeFlags": ["missing_evse_id", "duplicate_evse_secondary"],
  "includeConnectors": true,
  "maxItems": 0
}
```

#### Recipe 2: station-level coverage for Madrid province

One row per station for postal codes starting with 28, DC fast charging only.

```json
{
  "mode": "snapshot",
  "countries": ["ES"],
  "postalCodes": ["28"],
  "minPowerKw": 50,
  "includeConnectors": false,
  "maxItems": 0
}
```

### Why the history matters

| Source | EU EV Charging Graph |
|---|---|
| Germany publishes a monthly file that replaces the previous one | Compares every run with your previous run, per EVSE |
| France republishes a consolidated base daily, with repeated EVSE IDs from different producers | Keeps every row, gives repeated IDs distinct keys and flags them `duplicate_evse` |
| Spain publishes a DATEX II feed, no change flags | `change_type`: `baseline`, `new`, `changed`, `unchanged`, `removed` |
| Three formats, three encodings, three identifier styles | One schema, one EVSE ID format (`DE*ABC*E123` → `DEABCE123`) |

History starts the first time **you** run the actor: the first Delta run returns everything as `baseline`; every later run shows what changed since the previous one. Runs where no source has been republished finish in seconds and cost only the start fee.

### How to use EU EV Charging Graph

1. **Try it:** keep the pre-filled input (`snapshot`, Spain, 100 rows) and click **Start**. It takes about a minute.
2. **Get the whole inventory once:** choose *Delta* (or *Snapshot*), select the countries you need and set *Max items* to 0. The first run returns every charge point as `baseline`.
3. **Monitor weekly:** schedule the same input every week. France and Spain refresh daily, Germany monthly. Use the same *State key* (or the same filters) to keep one stream. With an explicit *State key* the filters are still part of the stream, and changing the countries or the one-row-per-station switch starts a new baseline.
4. **Instant bulk copy:** *Preloaded* returns the latest full DE+FR+ES snapshot prepared by Rastriq without any waiting, at a lower price per row (EVSE level, no change history). At launch, if that snapshot is not published yet, the run falls back to the live snapshot mode and is billed as `evse-record`.

The actor always reads the complete source of the countries you select (about 300 MB in total for all three); filters only decide which rows are returned. That is what makes `removed` reliable.

#### Input

| Field | Description |
|---|---|
| `mode` | `snapshot` (every charge point in scope, flagged against your last run), `delta` (default when empty: only new / changed / removed; first run = full `baseline`), `preloaded` (ready-made latest snapshot). |
| `countries` | `DE`, `FR`, `ES`. Empty = all three. |
| `includeConnectors` | `true` (default): one row per EVSE. `false`: one row per station with `evse_count`, the highest `power_kw` and the union of connector types. |
| `operators` | Optional. Operator name (matched on the normalised name) or 5-character EVSE operator prefix such as `DEENB`. |
| `minPowerKw` | Optional. Minimum power in kW (e.g. `50` for DC fast charging). |
| `postalCodes` | Optional. Postal codes or prefixes (`28`, `10115`, `75`). |
| `excludeFlags` | Optional. Leave out rows carrying these quality flags (for example `missing_evse_id`, `duplicate_evse`). By default nothing is filtered. |
| `allowMassChange` | Advanced. In *Delta*, a run where more than 10 % of a country's records are new, changed or removed stops with `status: mass_change` (nothing returned, state untouched) because that usually means a broken source file. Tick it only if you expect such a change. |
| `maxItems` | Stop after N rows (0 = no limit). A truncated first run does not save state. |
| `stateKey` | Name of your delta stream. By default derived from countries, level and filters, so different setups keep separate histories. |

#### Output

One flat row per charge point (or station). Main fields:

| Group | Fields |
|---|---|
| Keys | `record_key` (`source\|evse_id`), `evse_id`, `evse_id_raw`, `operator_id`, `operator_siren` (France), `source_record_id`, `station_id`, `country`, `source` (`bnetza`, `irve`, `nap_es`) |
| Operator | `operator_name_raw`, `operator_name_norm` |
| Place | `station_name`, `address`, `postal_code`, `city`, `region`, `lat`, `lon` |
| Charging | `connector_type` (`type2`, `ccs`, `chademo`, `domestic`…), `connector_type_raw`, `power_kw`, `status`, `commissioning_date`, `payment_methods` (common vocabulary: `app`, `card`, `cash`, `contactless`, `credit_card`, `debit_card`, `free`, `free_with_registration`, `online`, `pay_per_use`, `plug_and_charge`, `rfid`, `other`), `payment_methods_raw` (as published), `opening_hours` (OSM style, e.g. `Mo-Fr 08:00-18:00; Sa 09:00-12:00`), `opening_hours_raw` (the original text when it cannot be interpreted; `opening_hours` is then null) |
| History | `first_seen_at`, `last_seen_at`, `change_type`, `changed_fields`, `previous_values`, `source_updated_at`, `source_url` |
| Quality | `quality_flags`: `missing_evse_id`, `invalid_evse_format`, `duplicate_evse`, `coords_out_of_country`, `missing_coordinates`, `power_outlier`, `operator_from_developer`, `operator_possibly_natural_person`, `operator_name_redacted`, `location_coarsened`; for repeated EVSE IDs, every copy carries `duplicate_evse` and only the extra copies also carry `duplicate_evse_secondary` |

Example (Spain, changed power):

```json
{
  "record_key": "nap_es|ESALLEGO0000191",
  "level": "evse",
  "evse_id": "ESALLEGO0000191",
  "evse_id_raw": "ES*ALL*EGO0000191",
  "country": "ES",
  "source": "nap_es",
  "operator_name_raw": "Allego Spain S.L.U",
  "operator_name_norm": "allego",
  "operator_id": "ESALL",
  "station_name": "Allego RAFAEL ALMENAR S.A.",
  "postal_code": "46014",
  "city": "Xirivella",
  "lat": 39.462288,
  "lon": -0.405479,
  "connector_type": "ccs;chademo",
  "power_kw": 50.0,
  "payment_methods": ["app", "credit_card", "rfid"],
  "opening_hours": "24/7",
  "change_type": "changed",
  "changed_fields": ["power_kw"],
  "previous_values": { "power_kw": 22.0 },
  "quality_flags": []
}
```

Example (Germany, first run, real record). `evse_id` is `null` because the register publishes no EVSE ID for this point, so the row is flagged `missing_evse_id` and keeps a fallback key:

```json
{
  "record_key": "bnetza|1009242|1",
  "level": "evse",
  "evse_id": null,
  "evse_id_raw": null,
  "country": "DE",
  "source": "bnetza",
  "source_record_id": "1009242",
  "source_point_id": null,
  "station_id": "1009242",
  "evse_count": null,
  "evse_ids": null,
  "operator_name_raw": "Shell Deutschland GmbH",
  "operator_name_norm": "shell",
  "operator_id": null,
  "operator_siren": null,
  "station_name": null,
  "address": "Kiesgräble 2",
  "postal_code": "89129",
  "city": "Langenau",
  "region": "Baden-Württemberg",
  "lat": 48.49339,
  "lon": 10.10783,
  "connector_type": "ccs",
  "connector_type_raw": "DC Fahrzeugkupplung Typ Combo 2 (CCS)",
  "power_kw": 150,
  "status": "operational",
  "commissioning_date": "2023-07-25",
  "payment_methods": [
    "contactless",
    "credit_card",
    "debit_card",
    "online",
    "rfid"
  ],
  "payment_methods_raw": [
    "Kreditkarte (NFC)",
    "Debitkarte (NFC)",
    "Onlinezahlungsverfahren",
    "RFID-Karte"
  ],
  "opening_hours": null,
  "opening_hours_raw": null,
  "source_url": "https://data.bundesnetzagentur.de/Bundesnetzagentur/DE/Fachthemen/ElektrizitaetundGas/E-Mobilitaet/Ladesaeulenregister_BNetzA_2026-09-01.csv",
  "source_updated_at": "2026-09-01",
  "first_seen_at": "2026-10-12T15:59:53Z",
  "last_seen_at": "2026-10-12T15:59:53Z",
  "change_type": "baseline",
  "changed_fields": [],
  "previous_values": null,
  "quality_flags": [
    "missing_evse_id"
  ]
}
```

#### What the sources contain (measured 12 Oct 2026)

| Country | Source | Size | Rows |
|---|---|---|---|
| Germany | Bundesnetzagentur Ladesäulenregister (CSV, monthly) | 56 MB | about 116,000 stations, 209,000 charge points. Only about 65,000 points carry an EVSE ID in the register; the others get a fallback key and the flag `missing_evse_id`. |
| France | Base nationale des IRVE (data.gouv.fr, daily) | 159 MB | about 225,000 rows, 168,000 distinct EVSE IDs; about 57,000 IDs are repeated by different producers (flag `duplicate_evse`). |
| Spain | DGT National Access Point, DATEX II (daily) | 83 MB | about 12,000 sites, 35,500 charge points. |

A full DE+FR+ES run returns about 470,000 rows.

### EU EV Charging Graph input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `delta` when empty (pre-filled: `snapshot`) | `snapshot`, `delta` or `preloaded`. |
| `countries` | array of strings | empty = all three (pre-filled: `["ES"]`) | `DE`, `FR`, `ES`. |
| `includeConnectors` | boolean | `true` | `true`: one row per EVSE. `false`: one row per station with `evse_count`, the highest `power_kw` and the union of connector types. |
| `operators` | array of strings | empty = all | Operator name (normalised match) or 5-character EVSE operator prefix such as `DEENB`. |
| `minPowerKw` | number (min 0) | empty | Minimum power in kW. Points without a published power are excluded when this is set. |
| `postalCodes` | array of strings | empty = all | Full postal codes or prefixes (`28`, `10115`, `75`). |
| `excludeFlags` | array of strings | empty (nothing excluded) | Leave out rows carrying these flags: `missing_evse_id`, `duplicate_evse`, `duplicate_evse_secondary`, `invalid_evse_format`, `coords_out_of_country`, `missing_coordinates`, `power_outlier`, `operator_possibly_natural_person`. Excluded rows are not billed. |
| `maxItems` | integer (min 0) | `0` = no limit (pre-filled: `100`) | Stop after this many rows. |
| `stateKey` | string | empty | Name of your delta stream. |
| `allowMassChange` | boolean | `false` | Delta safety net, see the table above. |

Pre-filled input (Spain, 100 rows):

```json
{"mode": "snapshot", "countries": ["ES"], "maxItems": 100}
```

Weekly delta for fast chargers in Germany only:

```json
{"mode": "delta", "countries": ["DE"], "minPowerKw": 50, "excludeFlags": ["missing_evse_id"], "maxItems": 0}
```

### 💰 How much does it cost to use EU EV Charging Graph?

EU EV Charging Graph uses pay-per-event pricing. Prices per 1,000 events on the BRONZE tier:

| Event | Price |
|---|---|
| `actor-start` | $0.01 per run (4 GB memory) |
| `evse-record` (one row in *Snapshot* mode, or the first *Delta* baseline) | $0.50 per 1,000 rows |
| `evse-change` (one new / changed / removed row in *Delta* mode) | $2.00 per 1,000 rows |
| `evse-record-preloaded` (one row of the ready-made snapshot) | $0.30 per 1,000 rows |

Apify plan discounts apply to these prices: Free plan +15 %, Silver -15 %, Gold / Platinum / Diamond -30 %.

**Real-cost examples**

- **Spain:** about 35,500 charge points is roughly **$17.75** as a first baseline.
- **Germany + France + Spain:** about 470,400 rows is roughly **$235** (about **$141** as `preloaded`).
- **Cleaner extract:** with `excludeFlags` set to `missing_evse_id` and `duplicate_evse_secondary` the same three countries leave about 268,000 rows, roughly **$134**. Dropping every copy of repeated IDs (`duplicate_evse`) leaves about 209,000 rows, roughly $104.
- **Weekly delta:** you pay only for the changed rows, plus the start fee.
- **No source republished:** the run ends in seconds with **no per-row charge**, only the Actor start fee.

**Worked example (BRONZE tier):** a 1,000-row snapshot is 1,000 x $0.0005 = $0.50, plus the Actor start (4 GB = 4 x $0.0025 = $0.01): **$0.51**. A weekly delta that returns 100 changed points is 100 x $0.002 = $0.20 plus $0.01: **$0.21**. The same 1,000 rows as `preloaded` cost 1,000 x $0.0003 = $0.30 plus $0.01: $0.31.

Use `countries`, `minPowerKw` and `excludeFlags` to pay only for the rows you need, and run a small test (*Max items* 100) first.

### Sources and licences

- **Germany:** Bundesnetzagentur, Ladesäulenregister, licence **CC BY 4.0**. Attribution: **"Bundesnetzagentur.de"**.
- **France:** Base nationale des Infrastructures de Recharge pour Véhicules Électriques (IRVE), data.gouv.fr, **Licence Ouverte / Open Licence (Etalab)**. Attribution: source *data.gouv.fr – Base nationale des IRVE*, with the date of the extraction. data.gouv.fr has announced that this dataset will be withdrawn on 31 December 2026 and replaced by the national consolidation published by the Point d'Accès National (transport.data.gouv.fr). If the source disappears, the actor stops with a clear message instead of returning partial data.
- **Spain:** DGT, Punto de Acceso Nacional (NAP-ES), electric charging points in DATEX II. Reuse terms of the NAP dataset have **not been verified** by us; check the licence shown for the dataset on nap.dgt.es before redistributing the data commercially. Source: *DGT – NAP*.

You are responsible for including the attributions above when you republish the data. EU EV Charging Graph only adds normalisation, flags and change tracking.

### Personal data and redaction

- **France:** the source also contains contact e-mails and phone numbers of developers and operators. They are never read or returned.
- **Germany (and any source):** some registered operators are private individuals or sole traders. When an operator name looks like a natural person (a heuristic based on the name pattern and sole-trader markers such as "e.K." or "Einzelunternehmen"), the actor **always redacts it, and this cannot be switched off**: `operator_name_raw`, `operator_name_norm` and `station_name` are `null`, and `address` is `null` if it contains that name. `address` is also `null`, and the coordinates are **rounded to 3 decimals (about 100 m)** because the site may be a private home. The row is **still returned** with its EVSE ID, power, connectors, postal code, city and EVSE-derived `operator_id`, and carries the flags `operator_possibly_natural_person`, `operator_name_redacted` and `location_coarsened`. The name is dropped while the file is parsed, so it is never stored in your delta state or in the logs.
- E-mail addresses and phone numbers that some German operator names carry glued to them are stripped from every text field.
- The heuristic prefers over-redaction to leaks (some company names are redacted too), but it can still miss some names and can flag a few real companies; if a row you expected to name an operator is redacted, the flag tells you why. Use `excludeFlags` with `operator_possibly_natural_person` if you prefer to leave those rows out altogether.

### FAQ

**Is it legal to use this data?** The three registers are official open data published for reuse; see the licences above. Check the Spanish terms and your own obligations (attribution, personal data) before commercial redistribution.

**Why do some German rows have no `evse_id`?** The register does not require an EVSE ID for every charge point. Those rows keep a stable fallback key (`bnetza|<station id>|<position>`) and the flag `missing_evse_id`.

**Why do I see the same EVSE ID twice?** Different producers publish the same ID (typical in France). All copies are kept, flagged `duplicate_evse`, with distinct keys.

**What happens when a source does not change?** In *Delta* mode the run ends in seconds with `status: source_unchanged` and no results are charged.

**What if a source loses more than 10 % of its rows?** The delta is aborted (`source_anomaly`), the previous state is kept and nothing is marked as removed. The same happens with `mass_change` when more than 10 % of a country's records change at once, and with `source_format_changed` when a file no longer has the expected columns or structure.

**How do I keep the cost down?** Set `countries`, `minPowerKw`, `postalCodes` and `excludeFlags` to return only the rows you need (excluded rows are not billed), start with *Max items* 100, and use *Delta* for repeat runs: unchanged charge points are free.

**Why are some fields `null`?** Fields the source does not publish stay `null`: in the real German record above `evse_id`, `operator_id`, `station_name` and `opening_hours` are `null`. Check `quality_flags` for the reason where one exists.

### Related Actors

- [EU Pharma Sites Graph](https://apify.com/rastriq/eu-pharma-sites-graph) – EU pharma manufacturers and distributors from EMA EudraGMDP.
- [Google Maps Scraper - Places](https://apify.com/rastriq/gmaps-discover-enrich) - Google Maps places data.
- [Idealista Scraper](https://apify.com/rastriq/idealista-scraper) - real estate listings in Spain, Portugal and Italy.

*Built by Rastriq.*

# Changelog

This Actor's version history is a separate document: https://apify.com/rastriq/eu-ev-charging-graph/changelog.md

# Actor input Schema

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

<b>Snapshot</b>: every charge point in scope, flagged new / changed / unchanged / removed against your last run. <b>Delta</b> (default when empty): only what changed; the first run is a full <code>baseline</code>. <b>Preloaded</b>: instant, cheaper copy of the latest full DE+FR+ES snapshot published by the author (no change history, always EVSE level).

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

Registers to read. Empty = all three. Germany is about 209,000 charge points, France about 225,000 rows (168,000 distinct EVSE IDs), Spain about 35,500.

## `includeConnectors` (type: `boolean`):

On (default): one row per EVSE / charge point. Off: one row per station, with <code>evse_count</code>, the highest <code>power_kw</code> and the union of connector types.

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

Optional. Operator name (matched on the normalised name: ignores case, accents, punctuation and legal form, so <code>Tesla</code> = <code>Tesla Germany GmbH</code> = <code>TESLA France SARL</code>) or the 5-character EVSE operator prefix (<code>DEENB</code>, <code>FRFR1</code>). Empty = all operators.

## `minPowerKw` (type: `number`):

Optional. Keep charge points with at least this power (e.g. <code>50</code> for DC fast charging). Points without a published power are excluded when this is set.

## `postalCodes` (type: `array`):

Optional. Full postal codes or prefixes (<code>28</code> = Madrid province, <code>10115</code> = Berlin Mitte, <code>75</code> = Paris). Empty = all.

## `excludeFlags` (type: `array`):

Optional. Rows are never dropped by default: problems are only <b>flagged</b> in <code>quality_flags</code>. Pick flags here to leave those rows out of the output (and out of your bill). Typical: <code>missing_evse_id</code> (about 143,000 German points have no EVSE ID in the register) and <code>duplicate_evse_secondary</code> (the French base repeats about 57,000 EVSE IDs published by different producers: this flag is only on the extra copies, so one row per ID is kept; <code>duplicate_evse</code> would remove every copy, including the first).

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

Stop after this many rows (0 = no limit). A truncated first run does not save state.

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

Name of your delta stream. Runs sharing the same key compare against each other. By default the stream is derived from countries, level and filters, so different setups keep separate histories. With an explicit key, the operator / power / postal-code / exclude-flag filters are still part of the stream name, and changing <i>countries</i> or <i>One row per charge point</i> starts a new baseline (a note in RUN_SUMMARY says so).

## `allowMassChange` (type: `boolean`):

Safety net for <i>Delta</i>: if more than 10% of a country's records are new, changed or removed in one run, the run stops with <code>status: mass_change</code> (nothing emitted, state not updated) because that usually means a broken source file. Tick this only if you expect such a change.

## Actor input object example

```json
{
  "mode": "snapshot",
  "countries": [
    "ES"
  ],
  "includeConnectors": true,
  "maxItems": 100,
  "allowMassChange": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "mode": "snapshot",
    "countries": [
        "ES"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("rastriq/eu-ev-charging-graph").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 = {
    "mode": "snapshot",
    "countries": ["ES"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("rastriq/eu-ev-charging-graph").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 '{
  "mode": "snapshot",
  "countries": [
    "ES"
  ],
  "maxItems": 100
}' |
apify call rastriq/eu-ev-charging-graph --silent --output-dataset

```

## MCP server setup

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

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/Jgh3lOINpwOKMJgIP/builds/VmJTOVgnKvw25pnNP/openapi.json
