Endpoint reference

API documentation

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.

Basics

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.

ProtocolUse HTTPS. Plain HTTP is answered rather than redirected, so a mistyped scheme will silently work unencrypted — pin https:// in your client.
CurrencyEUR per litre, tax inclusive, in every market — local currencies are converted at the daily central-bank rate.
TimestampsUTC, format YYYY-MM-DD HH:MM:SS, except board_now.php which returns a Unix epoch.
NumbersPrices are rounded to 3 decimals. Most endpoints emit JSON numbers; action=brands returns its aggregates as strings — cast before you compare.
CORSAccess-Control-Allow-Origin: * on every endpoint below except board_now.php, which sends no CORS header and is therefore server-side only.
CachingSet 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.

Authentication

There is none today, and we would rather say so than pretend.

State of play, 8 September 2026. Every endpoint on this page is open and unauthenticated. API keys arrive together with card payment; until then, Pro and Business access is arranged by e-mail and the quotas on the pricing page describe the plan you agree to, not a counter that cuts you off. When keys land, they will be a X-API-Key request header and the open endpoints will keep working at the Free quota, so nothing you build today breaks.

Rate limits

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.

Errors

Live endpoints answer with {"ok": false, "error": "..."}; the history endpoint uses a bare {"error": "..."}. Check the HTTP status, not the shape.

StatusWhenBody
200Success. An empty result set is still a 200 — check the array length.{"ok":true,"data":{…}}
400Missing or invalid parameter, or an unknown action.{"ok":false,"error":"country parameter required"}
404Wrong path. You get the site’s HTML 404 page, not JSON.HTML
429Too many requests. Today only the natural-language endpoint enforces this, at 60 a minute per IP.{"error":"…"}
500Database 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"}

Live prices

Country summary Free

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.

GET/live/api.php?action=summary
ParameterRequiredMeaning
actionyessummary

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
      }
    ]
  }
}

Brand breakdown Pro

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.

GET/live/api.php?action=brands&country=GB&fuel=diesel
ParameterRequiredMeaning
countryyesISO 3166-1 alpha-2, upper or lower case. 400 if missing.
fuelnoOne of the fuel codes below. Defaults to diesel.
Known gap. France has no brand strings in the source feed, so 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" }
    ]
  }
}

Nearby stations Pro

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.

GET/live/api.php?action=stations&country=FR&lat=48.85&lng=2.35&radius=10&limit=3
ParameterRequiredMeaning
countryyesISO alpha-2. 400 if missing.
lat, lngyesDecimal degrees, WGS 84. Both must be non-zero or you get a 400.
radiusnoKilometres. Default 10, silently capped at 50.
limitnoNumber of stations. Default 20, silently capped at 100.
fuelnoAccepted 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" }
        }
      }
    ]
  }
}

Price board Free

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.

GET/live/board_now.php

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.

No CORS here. This is the one endpoint that does not send 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.

Map layer Pro

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.

GET/live/geojson.php?country=SI&fuel=diesel&bounds=45.4,13.3,46.9,16.6&limit=3
ParameterRequiredMeaning
countrynoISO alpha-2. Defaults to FR.
fuelnoDefaults to diesel.
boundsnosouth,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.
limitnoDefault 1500, clamped to 10–5000.
sortnoasc (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, … }
}
Licence flag. 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.

Pipeline status Free

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.

GET/live/api.php?action=stats

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.

History & extras

Weekly national history Free

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.

GET/lang/api/country-history.php?cc=DE
ParameterRequiredMeaning
ccyesTwo letters, case-insensitive. EL and GR both return Greece. Anything else gives a 400.
Window. This endpoint returns the last five years. The database holds 28,754 weekly points for 29 countries going back to 2005 — the full series ships as a bulk file on Business, or ask and we cut you the years you need.

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.

Natural-language query Free

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.

GET/api/natural-language/?q=cheapest+diesel+in+Germany&format=compact
ParameterRequiredMeaning
qyesThe 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.
formatnofull (default), compact, or csv — CSV comes with a commented provenance header.
POST bodyno{"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 }
    ]
  }
}

Tolls & barriers Free

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.

GET/api/toll/get_json.php

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
    }
  ]
}

Codes & units

Fuel codes, ordered by how much data we hold on each.

CodeFuelNotes
dieselDiesel / gasoilEvery live market.
sp95Unleaded 95 (Euro 95)Every live market except GB, where E10 has replaced it.
sp98Unleaded 98Most western markets.
e10Petrol, 10% ethanolFR, GB, AU.
e5Petrol, 5% ethanolGB, AU.
gplLPG / autogasAD, ES, FR, GR, HR, IT, LT, PT, SI.
e85Superethanol E85FR.
cngCompressed natural gasIT.
lngLiquefied natural gasIT, thin coverage.
hvoHydrotreated vegetable oilGB, 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.

Changelog

DateChange
2026-09-08First published reference. Nine endpoints documented against captured responses; OpenAPI 3 spec added at /api/openapi.json.
2026-09-05Live 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-25last_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.

Next step

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.