# European Fuel Prices Scraper - Italy, Spain, France (`koukkis/european-fuel-prices`) Actor

Petrol, diesel, LPG, CNG and HVO prices for 42,000+ filling stations in Italy, Spain and France, straight from the three governments' official open data. One comparable fuel taxonomy, explicit units and radius search. You pay only for the price records you receive.

- **URL**: https://apify.com/koukkis/european-fuel-prices.md
- **Developed by:** [Koukkis](https://apify.com/koukkis) (community)
- **Categories:** Other, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 price 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?

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

## European Fuel Prices — Italy, Spain, France

Petrol, diesel, LPG, CNG and HVO prices for **42,000+ filling stations** in Italy,
Spain and France, taken straight from the three governments' own open-data feeds
and normalised into one comparable dataset.

Most fuel price scrapers cover the United States and read crowd-reported figures.
This one covers Europe and reads official sources: the Italian Ministry of
Enterprises and Made in Italy (MIMIT), the Spanish Ministry for the Ecological
Transition (MITECO), and the French government open-data portal.

### What you get

| | |
|---|---|
| Stations | 42,000+ across IT, ES, FR |
| Price records | ~167,000 per full run (one record = one fuel at one station) |
| Fuels | 18 normalised categories, original label kept |
| Freshness | France: updated about every 10 minutes. Spain: about every 30 minutes. Italy: once a day, prices in force at 8:00 the previous day |
| Coordinates | latitude and longitude, for maps and radius searches |
| Source | official government open data only |

### Pricing

You pay per **price record delivered** to your dataset: **$0.50 per 1,000
records** ($0.0005 each, event `price-record`). There is no start fee.

You do **not** pay for:

- records your filters remove,
- a country whose feed was unreachable — the other countries are still delivered,
- a run that returns nothing, or that fails,
- invalid input — the run stops before fetching anything.

Set **Maximum cost per run** in the run options and the Actor delivers exactly as
many records as that budget covers, then finishes normally. It never goes over.

| Typical input | Records | Cost |
|---|---:|---:|
| Default run: 1,000 records shared across IT, ES, FR | 1,000 | $0.50 |
| Diesel within 5 km of central Madrid | ~60 | $0.03 |
| Diesel and petrol 95 within 25 km of Madrid | ~1,260 | $0.63 |
| Self-service diesel and petrol 95 within 25 km of Milan | ~1,650 | $0.83 |
| Every diesel price in Spain | ~11,300 | $5.65 |
| Everything, all three countries | ~167,000 | $83.50 |

Running the Madrid example once a day costs about **$19 a month**. Narrow the
countries, fuels and radius to pay only for what you use.

### Input

Every field is optional. Running with defaults returns 1,000 records shared
evenly between the three countries.

| Field | Type | Default | Notes |
|---|---|---|---|
| `countries` | array | `["IT","ES","FR"]` | Which feeds to query |
| `fuels` | array | all | Normalised categories, e.g. `["diesel","lpg"]` |
| `latitude` / `longitude` | string | — | Centre of a radius search, decimal degrees. Give both or neither |
| `radiusKm` | integer | 25 | 1–500, used with latitude and longitude |
| `maxResults` | integer | 1000 | Shared across countries. `0` = no limit (~167,000) |
| `includeSuspectPrices` | boolean | false | See data quality below |
| `selfServiceOnly` | boolean | false | Italy only; see below |

An input the Actor cannot run as given — only a latitude, a coordinate that is
not a number, an unknown fuel — fails immediately with a clear message, instead
of quietly returning (and billing) records you did not ask for.

#### Cheapest diesel near a point

```json
{
  "countries": ["ES"],
  "fuels": ["diesel"],
  "latitude": "40.4168",
  "longitude": "-3.7038",
  "radiusKm": 5
}
```

#### One fuel across all three countries

```json
{ "fuels": ["diesel"], "maxResults": 0 }
```

### Example output

```json
{
  "country": "ES",
  "station_id": "ES-10943",
  "source_station_id": "10943",
  "name": "CEPSA",
  "brand": "CEPSA",
  "address": "PASEO MORET, 7",
  "city": "Madrid",
  "region": "MADRID",
  "postcode": "28008",
  "lat": 40.432861,
  "lon": -3.724194,
  "fuel": "diesel",
  "fuel_original": "Gasoleo A",
  "fuel_mapping_exact": true,
  "is_premium": false,
  "price": 2.034,
  "currency": "EUR",
  "unit": "EUR/l",
  "price_updated_at": "2026-09-26T14:10:27",
  "self_service": null,
  "price_suspect": false,
  "source": "Ministerio para la Transición Ecológica y el Reto Demográfico (MITECO)",
  "source_license": "Spanish public-sector information reuse terms (Ley 37/2007, RD 1495/2011): cite the source and the date of last update",
  "source_url": "https://geoportalgasolineras.es/",
  "fetched_at": "2026-09-26T12:10:39+00:00"
}
```

### Run it every day

The national feeds publish only the current price, never yesterday's. To build a
price history, schedule the Actor:

1. Save your input as a **task**.
2. In **Schedules**, create a schedule for that task that runs once a day, for
   example `0 9 * * *` for 9:00 every morning.
3. Set **Maximum cost per run** on the task so a schedule can never surprise you.

Each run lands in its own dataset, which exports as JSON, CSV, Excel or XML and
connects to Make, Zapier, Google Sheets and Airtable. From code, one call runs
the Actor and returns the records:

```bash
curl -X POST "https://api.apify.com/v2/acts/koukkis~european-fuel-prices/run-sync-get-dataset-items" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"countries":["FR"],"fuels":["diesel"],"latitude":"48.8566","longitude":"2.3522","radiusKm":10}'
```

### Why the fuel taxonomy matters

The same physical fuel has a different name in every country, and Italian
retailers brand premium diesel under a dozen names of their own — Blue Diesel,
Supreme Diesel, Hi-Q Diesel, Excellium, V-Power, DieselMax. Raw national feeds
cannot be compared with each other.

This Actor maps **80+ national labels onto 18 categories** and returns both, so
you can group across borders without losing the original:

| Normalised `fuel` | Italy | Spain | France |
|---|---|---|---|
| `diesel` | Gasolio | Gasoleo A | Gazole |
| `diesel_premium` | Blue Diesel, Hi-Q Diesel, Supreme Diesel | Gasoleo Premium | — |
| `petrol_95` | Benzina | Gasolina 95 E5 | SP95 |
| `petrol_98` | Benzina speciale | Gasolina 98 E5 | SP98 |
| `hvo` | HVOlution, HVO100 | Diésel Renovable | — |
| `lpg` | GPL | Gases licuados del petróleo | GPLc |
| `cng` | Metano | Gas Natural Comprimido | — |

A label the mapping does not recognise is returned as `unknown` and flagged with
`fuel_mapping_exact: false`. It is never guessed into the wrong bucket, because a
silent misclassification is worse than a visible gap.

### Data quality

**Gases are not sold by the litre.** CNG, LNG and hydrogen are priced per
kilogram, but all three national feeds publish them in the same numeric column as
litre prices. Treated naively, hydrogen looks like petrol at €17.45 per litre.
Every record carries an explicit `unit` of either `EUR/l` or `EUR/kg`.

**Official feeds contain typos.** Around 10 records in 167,000 are clearly wrong —
diesel listed at €8.888, premium petrol at €0.100. They are flagged with
`price_suspect: true` and excluded by default, never silently deleted. Set
`includeSuspectPrices: true` to receive them.

**Some prices are old.** A station that has not reported a change keeps its last
price. Check `price_updated_at`: in Italy and France it is the time of that
particular price; in Spain it is the time the whole feed was published, so it
says nothing about an individual price's age.

**Broken coordinates are repaired or removed.** The feeds contain placeholders
such as 0,0, swapped latitude and longitude, and longitudes of 999. A clear swap
is corrected; anything else outside the country becomes `null`, so the station
still appears in the data but not in radius searches.

**An incomplete feed is retried and reported.** The French portal occasionally
serves a partial export while it re-indexes. The Actor compares the row count
with the portal's own figure, retries, and if France is still incomplete it
delivers what was published and says so in the log.

Every run ends with a compact quality report in the log and saves the same
figures as `QUALITY_REPORT` in the run's key-value store: records and stations
per country, implausible prices flagged, how many delivered prices are older than
30 days, records without coordinates, and any source that failed.

### Self-service versus served (Italy)

Italy publishes two prices per pump, self-service and served, and the difference
is often 15–20 cents. `self_service` is `true` or `false` for Italian records and
`null` for Spain and France, whose feeds do not make the distinction. Set
`selfServiceOnly: true` to keep only self-service prices; Spanish and French
records are unaffected by that flag.

### Data sources, licences and attribution

| Country | Publisher and dataset | Licence and what it requires |
|---|---|---|
| Italy | Ministero delle Imprese e del Made in Italy (MIMIT) — [Carburanti: prezzi praticati e anagrafica degli impianti](https://www.mimit.gov.it/it/open-data/elenco-dataset/carburanti-prezzi-praticati-e-anagrafica-degli-impianti) | [Italian Open Data License 2.0](https://www.dati.gov.it/content/italian-open-data-license-v20). Credit the source and the licensor and link the licence. Commercial reuse allowed |
| Spain | Ministerio para la Transición Ecológica y el Reto Demográfico (MITECO) — [Geoportal Gasolineras](https://geoportalgasolineras.es/) | Spanish public-sector information reuse rules (Ley 37/2007, Real Decreto 1495/2011 art. 7). Do not distort the data, cite the source, give the date of the last update |
| France | DGCCRF via [data.economie.gouv.fr — Prix des carburants en France, flux instantané v2](https://data.economie.gouv.fr/explore/dataset/prix-des-carburants-en-france-flux-instantane-v2/) | [Licence Ouverte / Open Licence 2.0 (Etalab)](https://www.etalab.gouv.fr/wp-content/uploads/2017/04/ETALAB-Licence-Ouverte-v2.0.pdf). Credit the source and the date of the last update; a link to the data is enough. Commercial reuse allowed |

To make attribution easy to keep, **every record carries it**: `source`
(publisher), `source_license`, `source_url` and `price_updated_at` (the date of
the last update). If you republish the data, keep those fields or credit the
publishers the same way. None of the three licences allow presenting the data as
official or as endorsed by the publisher, and this Actor is not affiliated with
or endorsed by any of them. The French portal's region and department fields are
derived from INSEE, IGN and Natural Earth reference data.

Germany is deliberately absent. The only practical German feed restricts bulk
access to one request per minute and licenses historical data for
non-commercial use only, so it cannot back a commercial dataset.

### Disclaimer

The data comes from official public sources and is delivered as published. It
can contain the sources' own errors, omissions and delays, and a station's price
at the pump may differ from the published one. No warranty is given as to its
accuracy, completeness or timeliness. Do not use it as the sole basis for
financial or safety-related decisions. You are responsible for how you use the
data, including complying with the source licences above.

### Use cases

- Fleet and logistics routing by real fuel cost rather than distance
- Price comparison apps and cheapest-station finders
- Cross-border price research and journalism
- Inflation and energy-cost tracking
- Competitor price monitoring for fuel retailers

# Actor input Schema

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

Which national open-data feeds to query. Leave empty for all three.

## `fuels` (type: `array`):

Normalised fuel categories. Leave empty to return every fuel. National brand names such as Blue Diesel, Hi-Q Diesel or Gasoleo Premium are mapped onto these automatically.

## `latitude` (type: `string`):

Optional centre point for a radius search, in decimal degrees, e.g. 40.4168. Give it together with longitude; a run with only one of them, or with a value that is not a number, fails before anything is fetched or charged.

## `longitude` (type: `string`):

Decimal degrees, e.g. -3.7038. Used together with latitude.

## `radiusKm` (type: `integer`):

Search radius around the given point.

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

The most price records to deliver, and pay for. When several countries are selected the limit is shared between them, so the default run shows all three. Set to 0 for no limit: about 167,000 records for all three countries. Your maximum cost per run caps this too.

## `includeSuspectPrices` (type: `boolean`):

National feeds contain occasional data-entry errors, for example diesel listed at 8.888 EUR/l. They are always flagged with price_suspect rather than silently deleted, and excluded unless you turn this on.

## `selfServiceOnly` (type: `boolean`):

Italy publishes both self-service and served prices for the same pump. Spain and France do not distinguish them and are unaffected.

## Actor input object example

```json
{
  "countries": [
    "IT",
    "ES",
    "FR"
  ],
  "fuels": [],
  "radiusKm": 25,
  "maxResults": 1000,
  "includeSuspectPrices": false,
  "selfServiceOnly": false
}
```

# Actor output Schema

## `prices` (type: `string`):

One record per fuel at one station: price, unit, update time, location and source attribution.

## `qualityReport` (type: `string`):

Records and stations per country, implausible and old prices, records without coordinates and any source that failed.

# 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("koukkis/european-fuel-prices").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("koukkis/european-fuel-prices").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 koukkis/european-fuel-prices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,koukkis/european-fuel-prices"
        }
    }
}
```

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/xlfnon4cBthAUp9T8/builds/avHvUeDXEWImy3YV8/openapi.json
