Nine read-only JSON endpoints over European fuel prices, stations, tolls and twenty years of weekly national history. No SDK, no OAuth dance — plain HTTPS GET. Every response shown below was captured from the live service.
Base URL https://www.fuel-prices.eu. All endpoints are
GET and return UTF-8 JSON. All but one send
Access-Control-Allow-Origin: *, so they work straight from a browser.
| Protocol | Use HTTPS. Plain HTTP is answered rather than redirected, so a mistyped scheme will silently work unencrypted — pin https:// in your client. |
| Currency | EUR per litre, tax inclusive, in every market — local currencies are converted at the daily central-bank rate. |
| Timestamps | UTC, format YYYY-MM-DD HH:MM:SS, except board_now.php which returns a Unix epoch. |
| Numbers | Prices are rounded to 3 decimals. Most endpoints emit JSON numbers; action=brands returns its aggregates as strings — cast before you compare. |
| CORS | Access-Control-Allow-Origin: * on every endpoint below except board_now.php, which sends no CORS header and is therefore server-side only. |
| Caching | Set per endpoint: max-age=300 on live/api.php, 120 on board_now.php and geojson.php, 900 on natural-language, 3600 on history, 86400 on tolls. Apache appends a second, longer Cache-Control: max-age=3600 — treat the shorter value as authoritative. |
There is none today, and we would rather say so than pretend.
X-API-Key request header and the open endpoints will keep working at the
Free quota, so nothing you build today breaks.
Plan quotas: 1,000 requests a month on Free, 50,000 on Pro, 500,000 on Business, with burst ceilings of 60, 600 and 3,000 an hour.
Because there are no keys yet, those numbers are not machine-enforced. What is enforced:
the natural-language endpoint enforces its own ceiling of 60 requests a minute per IP
(it declares it in its own rate_limits field), and abusive traffic is dropped
at the edge. Cache responses for at least their max-age, set a real
User-Agent that names your product, and you will never see a limit.
Live endpoints answer with {"ok": false, "error": "..."};
the history endpoint uses a bare {"error": "..."}. Check the HTTP status,
not the shape.
| Status | When | Body |
|---|---|---|
| 200 | Success. An empty result set is still a 200 — check the array length. | {"ok":true,"data":{…}} |
| 400 | Missing or invalid parameter, or an unknown action. | {"ok":false,"error":"country parameter required"} |
| 404 | Wrong path. You get the site’s HTML 404 page, not JSON. | HTML |
| 429 | Too many requests. Today only the natural-language endpoint enforces this, at 60 a minute per IP. | {"error":"…"} |
| 500 | Database or upstream failure on our side. Retry with backoff. | {"ok":false,"error":"…"} |
Real 400, captured 8 September 2026
$ curl -s -i "https://www.fuel-prices.eu/live/api.php?action=brands" | head -1
HTTP/2 400
$ curl -s "https://www.fuel-prices.eu/live/api.php?action=brands"
{"ok":false,"error":"country parameter required"}
$ curl -s "https://www.fuel-prices.eu/live/api.php?action=nope"
{"ok":false,"error":"Unknown action: nope"}
$ curl -s "https://www.fuel-prices.eu/lang/api/country-history.php?cc=XYZ"
{"error":"Invalid country code"}
National average, minimum and maximum per fuel type, with the number of contributing stations and the freshness stamp. This is the call most integrations start with.
| Parameter | Required | Meaning |
|---|---|---|
| action | yes | summary |
Returns every live market in one document (about 19 KB). Germany is excluded: we hold
the data but do not publish it yet. last_fetched is UTC, normalised from the
database clock, and is the number to trust when you decide whether a price is stale.
Response (2 of 21 markets shown)
{
"ok": true,
"data": {
"countries": [
{
"country_code": "AD",
"fuels": {
"diesel": { "stations": 54, "min": 1.619, "max": 1.64, "avg": 1.629 },
"gpl": { "stations": 1, "min": 0.876, "max": 0.876, "avg": 0.876 },
"sp95": { "stations": 54, "min": 1.609, "max": 1.628, "avg": 1.616 },
"sp98": { "stations": 54, "min": 1.669, "max": 1.698, "avg": 1.678 }
},
"last_fetched": "2026-09-07 21:07:07",
"total_stations": 54
},
{
"country_code": "AT",
"fuels": {
"diesel": { "stations": 1722, "min": 1.799, "max": 2.449, "avg": 2.092 },
"sp95": { "stations": 1592, "min": 1.689, "max": 2.199, "avg": 1.889 }
},
"last_fetched": "2026-09-07 18:40:55",
"total_stations": 2378
}
]
}
}
Average, cheapest and dearest price per retail brand in one market, for one fuel. Brands with fewer than five stations carrying that fuel are left out, so a thin market can legitimately come back empty.
| Parameter | Required | Meaning |
|---|---|---|
| country | yes | ISO 3166-1 alpha-2, upper or lower case. 400 if missing. |
| fuel | no | One of the fuel codes below. Defaults to diesel. |
country=FR returns an empty brands array — that is the data,
not a fault. GB, IT, ES, PT, AT and GR are populated.Response (3 of many brands shown)
{
"ok": true,
"data": {
"country": "GB",
"fuel": "diesel",
"brands": [
{ "brand_name": "Go", "logo_file": null, "station_count": 6,
"price_min": "1.966", "price_max": "2.083", "price_avg": "2.043" },
{ "brand_name": "Thompson Fuels Ltd", "logo_file": null, "station_count": 6,
"price_min": "2.059", "price_max": "2.059", "price_avg": "2.059" },
{ "brand_name": "Costco Wholesale", "logo_file": null, "station_count": 18,
"price_min": "2.013", "price_max": "2.129", "price_avg": "2.060" }
]
}
}
Stations inside a radius, nearest first, each with every fuel price we currently hold and when the source last moved it. The workhorse of any “cheapest fuel near me” feature.
| Parameter | Required | Meaning |
|---|---|---|
| country | yes | ISO alpha-2. 400 if missing. |
| lat, lng | yes | Decimal degrees, WGS 84. Both must be non-zero or you get a 400. |
| radius | no | Kilometres. Default 10, silently capped at 50. |
| limit | no | Number of stations. Default 20, silently capped at 100. |
| fuel | no | Accepted for symmetry; the response carries every fuel the station sells. |
Response (first station of three)
{
"ok": true,
"data": {
"country": "FR",
"center": { "lat": 48.85, "lng": 2.35 },
"radius": 10,
"stations": [
{
"id": 6808,
"name": "Station PARIS",
"brand": null,
"logo": null,
"address": "8,10,10bis Rue Bailleul",
"city": "PARIS",
"postal_code": "75001",
"lat": 48.861,
"lng": 2.341,
"is_open": true,
"distance_km": 1.4,
"prices": {
"diesel": { "price": 2.68, "updated": "2026-09-04 21:30:24" },
"sp95": { "price": 2.49, "updated": "2026-09-04 21:30:24" },
"sp98": { "price": 2.57, "updated": "2026-09-04 21:30:24" }
}
}
]
}
}
The smallest useful call on the service: one flat object of national petrol and diesel averages plus a server epoch, under 500 bytes. Ideal for a ticker or a widget.
Response (truncated)
{
"ok": true,
"ts": 1788814863,
"petrol": { "AD": 1.616, "AT": 1.889, "DK": 2.392, "ES": 1.823, "FR": 2.166,
"GB": 1.935, "GR": 2.091, "HR": 1.675, "IS": 1.795, "IT": 2.056, … },
"diesel": { … }
}
ts is a Unix epoch in seconds — the only endpoint that does not use a
date string.
Access-Control-Allow-Origin, so a browser fetch() from another
origin is blocked. Call it server-side, or use
live/api.php?action=summary from the browser.A ready-to-drop FeatureCollection for Leaflet, Mapbox or anything else that
eats GeoJSON: one point per station, price and opening state in the properties, plus a
meta block with the price range in view.
| Parameter | Required | Meaning |
|---|---|---|
| country | no | ISO alpha-2. Defaults to FR. |
| fuel | no | Defaults to diesel. |
| bounds | no | south,west,north,east. Set it. The default box is centred on western Europe, so a country outside it returns zero features rather than an error. |
| limit | no | Default 1500, clamped to 10–5000. |
| sort | no | asc (cheapest first, default) or desc. |
Response (first feature)
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [16.0513559, 46.3896077] },
"properties": {
"id": 10075914,
"name": "West Oil BS Zavrč",
"brand": "West oil",
"address": "Zavrč 8c",
"city": "Zavrč",
"postal_code": "2283",
"price": 1.463,
"fuel": "diesel",
"is_open": true,
"updated": "2026-09-07 14:05:51",
"oh": "Mo-Fr 08:00-18:00; Sa-Su off",
"open": "closed",
"next": "08:00"
}
}
],
"meta": { "country": "SI", "fuel": "diesel", "returned": 3, "limited": false, … }
}
oh (opening hours) is enriched from
OpenStreetMap for 6,637 stations and therefore carries
ODbL 1.0 share-alike terms. Everything else in the feature is ours. See the
licence FAQ.Station and price counts per market, plus the last twenty ingest runs with row counts and durations. Use it for a health check before you trust a number.
Response (truncated)
{
"ok": true,
"data": {
"station_counts": { "AD": 54, "AT": 2378, "ES": 12417, "FR": 10109, "GB": 8085,
"IT": 22423, … },
"price_counts": { "AD": 163, "AT": 4227, "ES": 29943, "FR": 35572, … },
"recent_fetches": [
{
"country_code": "AD",
"source": "govern_ad",
"fetch_started_at": "2026-09-08 00:07:01",
"fetch_ended_at": "2026-09-08 00:07:07",
"stations_processed": 54,
"prices_updated": 163,
"status": "success",
"execution_time_sec": "6.00"
}
]
}
}
station_counts lists every market we hold stations for, including ones not
yet published in summary (Germany, Sweden). A market appearing here is not a
promise that it appears in summary.
Weekly Euro 95 and diesel prices for one country, as three parallel arrays. Source is the European Commission Weekly Oil Bulletin, which is why the series is directly comparable between countries.
| Parameter | Required | Meaning |
|---|---|---|
| cc | yes | Two letters, case-insensitive. EL and GR both return Greece. Anything else gives a 400. |
Response (ends elided)
{
"cc": "DE",
"labels": ["2021-09-13", "2021-09-20", … , "2026-08-31"],
"e95": [1.629, 1.636, … , 2.236],
"diesel": [1.401, 1.42, … , 2.232],
"count": 258
}
The three arrays are index-aligned. A week with no reading for one fuel carries
0, not null — filter those out before you average.
Built for AI agents: ask in English, get structured data back with the intent it parsed,
a confidence score and full provenance. Also accepts POST with a batch of
questions.
| Parameter | Required | Meaning |
|---|---|---|
| q | yes | The question. Also works as a path segment:
/api/natural-language/Vignette+prices+in+Austria. Encode spaces as
+, not %20 — the server rejects an encoded space in the
path with a 403 before the endpoint is reached. |
| format | no | full (default), compact, or csv — CSV comes with a commented provenance header. |
| POST body | no | {"query":"…"} or {"queries":["…","…"]} for batches. |
Response (truncated)
{
"status": "ok",
"query": "cheapest diesel in Germany",
"intent": "cheapest_ranking",
"confidence": 0.95,
"parsed": { "countries": ["DE"], "fuel_type": "diesel", "time_range_weeks": 12 },
"metadata": {
"generated_at": "2026-09-07T21:07:36+00:00",
"data_date": "2026-08-31",
"source": "European Commission Weekly Oil Bulletin",
"currency": "EUR",
"unit": "per liter, tax-inclusive",
"update_frequency": "weekly (Thursday after 15:00 EET)"
},
"message": "EU countries ranked by diesel price, cheapest first (2026-08-31). #1: Malta.",
"data": {
"ranking_top3": [
{ "rank": 1, "country_code": "MT", "country_name": "Malta", "price_euro95": 1.34, "price_diesel": 1.21 },
{ "rank": 2, "country_code": "PL", "country_name": "Poland", "price_euro95": 1.525, "price_diesel": 1.669 }
]
}
}
Every tolled tunnel, bridge, pass and mainline barrier we track in Europe, with the price in euro and in local currency, plus coordinates. One document, 546 records, cached for a day.
No parameters — you get the whole set (about 210 KB) and filter client-side on
country_code or type. Human-readable notes and the licence live
at /api/toll/docs.php.
Response (first record)
{
"count": 546,
"data": [
{
"id": 1445,
"country_code": "AD",
"type": "tunnel",
"barrier_type": "mainline",
"name": "Tunel d'Envalira",
"price_eur": 7.9,
"price_local": 7.9,
"currency": "EUR",
"info": "Connects Pas de la Casa (FR border)",
"lat": 42.545,
"lng": 1.715
}
]
}
Fuel codes, ordered by how much data we hold on each.
| Code | Fuel | Notes |
|---|---|---|
| diesel | Diesel / gasoil | Every live market. |
| sp95 | Unleaded 95 (Euro 95) | Every live market except GB, where E10 has replaced it. |
| sp98 | Unleaded 98 | Most western markets. |
| e10 | Petrol, 10% ethanol | FR, GB, AU. |
| e5 | Petrol, 5% ethanol | GB, AU. |
| gpl | LPG / autogas | AD, ES, FR, GR, HR, IT, LT, PT, SI. |
| e85 | Superethanol E85 | FR. |
| cng | Compressed natural gas | IT. |
| lng | Liquefied natural gas | IT, thin coverage. |
| hvo | Hydrotreated vegetable oil | GB, very thin coverage. |
Country codes are ISO 3166-1 alpha-2 throughout. Two things to
know: Greece is GR in live data but appears as EL in
Commission-sourced history (both are accepted on the history endpoint), and Australian
markets are grouped under AU with per-state sources.
| Date | Change |
|---|---|
| 2026-09-08 | First published reference. Nine endpoints documented against captured responses; OpenAPI 3 spec added at /api/openapi.json. |
| 2026-09-05 | Live prices moved behind a sanity filter: a station whose feed reports an implausible price now serves its last validated price, or nothing. Response shape unchanged. |
| 2026-07-25 | last_fetched in action=summary normalised to true UTC. Values previously read up to three hours fresher than reality. |
Breaking changes get announced by e-mail to everyone on a paid plan at least 30 days ahead. Additive fields can appear at any time — parse defensively and ignore keys you do not know.
Free needs no key: pick an endpoint above and call it. For station-level data, exports or an uptime commitment, see pricing and send the access form. Questions to hi@fuel-prices.eu.