Files
fuel-price/docs/api-reference.md
Ovidiu U 6a80c11f38
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
feat: add LLM prediction providers with structured output support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 14:42:44 +01:00

8.6 KiB
Raw Permalink Blame History

FuelAlert API Reference

Base URL: https://fuel-price.test/api
All endpoints return JSON. All endpoints require an X-Api-Key header.

Authentication:

X-Api-Key: your-secret-key

All requests without a valid key return 403 Forbidden.


Stations

GET /api/stations

Returns nearby petrol stations with live prices for a given fuel type.

Location — provide one of:

Parameter Type Description
postcode string UK postcode, outcode, or place name (resolved via postcodes.io)
lat + lng float Decimal lat/lng (required if no postcode)

Optional:

Parameter Type Default Notes
fuel_type string Required. See fuel type aliases below
radius float 10.0 Search radius in km (0.150)
sort string "price" "price", "distance", "updated", or "brand"
pricing_mode string "pump" (reserved, no effect yet)

Sort values:

Value Sorts by
price Price ascending (cheapest first) — default
distance Distance ascending (closest first)
updated Price freshness descending (most recently updated first)
brand Brand name AZ

Fuel type aliases (fuel_type accepts any of these):

Alias Maps to
petrol, unleaded, e10 E10 (standard unleaded)
premium_unleaded, e5 E5 (super unleaded)
diesel, b7_standard B7 Standard (standard diesel)
premium_diesel, b7_premium B7 Premium (premium diesel)
b10 B10 (biodiesel blend)
hvo HVO (hydrotreated vegetable oil)

Example request:

GET /api/stations?postcode=SW1A1AA&fuel_type=petrol&radius=5&sort=price
GET /api/stations?lat=51.5074&lng=-0.1278&fuel_type=diesel&radius=10&sort=distance
GET /api/stations?postcode=M11AE&fuel_type=petrol&sort=updated
GET /api/stations?postcode=M11AE&fuel_type=petrol&sort=brand

Response:

{
  "data": [
    {
      "station_id": "0028acef5f3afc41...",
      "name": "Alex Fuel Station",
      "brand": "BP",
      "is_supermarket": false,
      "address": "123 High Street, London",
      "postcode": "SW1A 1AA",
      "lat": 51.5074,
      "lng": -0.1278,
      "distance_km": 1.23,
      "fuel_type": "e10",
      "price_pence": 14390,
      "price": 143.9,
      "price_updated_at": "2026-04-05T08:00:00.000Z"
    }
  ],
  "meta": {
    "count": 12,
    "fuel_type": "e10",
    "radius_km": 10.0,
    "lowest_pence": 14290,
    "highest_pence": 14890,
    "cheapest_price_pence": 14290,
    "avg_pence": 14523.5
  }
}

Notes:

  • price_pence is the raw integer (e.g. 14390 = 143.90p). price is the float in pence (e.g. 143.9).
  • Closed stations (temporary or permanent) are excluded.
  • Every call logs a search record used by the stats endpoint.

Error — postcode not found:

{ "errors": { "postcode": ["Postcode not found."] } }

Error — missing location:

{ "errors": { "lat": ["The lat field is required when postcode is not present."] } }

Stats

GET /api/stats/searches

Aggregate search statistics. Useful for a "social proof" counter on the frontend.

Parameter Type Default Notes
period string "week" "week" (7 days) or "month" (30 days)

Example request:

GET /api/stats/searches
GET /api/stats/searches?period=month

Response:

{
  "total_searches": 3842,
  "unique_searchers": 1201,
  "avg_results": 14.3,
  "avg_lowest_price": 141.2,
  "avg_highest_price": 148.7,
  "avg_price": 144.9,
  "period": "week",
  "message": "Helped 1201 drivers find cheaper fuel this week so far!"
}

Notes:

  • avg_lowest_price, avg_highest_price, avg_price are in pence as floats (e.g. 141.2 = 141.2p).
  • unique_searchers is based on hashed IP — approximate unique users.

Prediction

GET /api/prediction

National or regional E10 fuel price direction forecast for the next 7 days, based on live price data signals. Always analyses E10 — the most widely available fuel and the one with the most price history.

Parameter Type Description
lat float Optional. Decimal latitude. Enables regional prediction (50km radius).
lng float Optional. Decimal longitude. Required if lat is provided.

Example request:

GET /api/prediction
GET /api/prediction?lat=51.5074&lng=-0.1278

Response:

{
  "fuel_type": "e10",
  "current_avg": 143.9,
  "predicted_direction": "down",
  "predicted_change_pence": -2.1,
  "confidence_score": 74.5,
  "confidence_label": "high",
  "action": "wait",
  "reasoning": "National prices have been falling at 0.3p/day. Supermarkets led last week — independents typically follow within 48h.",
  "prediction_horizon_days": 7,
  "region_key": "national",
  "methodology": "multi_signal_live_fallback",
  "signals": {
    "trend": {
      "score": -0.8,
      "confidence": 0.92,
      "direction": "down",
      "detail": "Slope: -0.30p/day over 5 days (R²=0.92) [Adaptive lookback active]",
      "data_points": 5,
      "enabled": true,
      "slope": -0.3,
      "r_squared": 0.92
    },
    "day_of_week": {
      "score": 0.0,
      "confidence": 0.0,
      "direction": "stable",
      "detail": "Insufficient history for day-of-week pattern.",
      "data_points": 0,
      "enabled": true
    },
    "brand_behaviour": {
      "score": -1.0,
      "confidence": 0.6,
      "direction": "down",
      "detail": "Supermarkets fell 3.5p vs majors 0.5p (divergence: 3.0p). Expect majors to follow.",
      "data_points": 2800,
      "enabled": true
    },
    "national_momentum": {
      "score": 0.0,
      "confidence": 0.0,
      "direction": "stable",
      "detail": "National momentum disabled for national predictions",
      "data_points": 0,
      "enabled": false
    },
    "regional_momentum": {
      "score": 0.0,
      "confidence": 0.0,
      "direction": "stable",
      "detail": "No coordinates provided for regional momentum analysis",
      "data_points": 0,
      "enabled": false
    },
    "price_stickiness": {
      "score": 0.05,
      "confidence": 0.8,
      "direction": "stable",
      "detail": "Sticky prices (avg hold: 5.2 days) — more predictable.",
      "data_points": 160,
      "enabled": true
    }
  }
}

region_key values:

Value Meaning
"national" No coordinates provided. current_avg and signals use national data. regional_momentum is disabled.
"regional" Coordinates provided. current_avg uses stations within 50km. regional_momentum is the primary signal (50% weight). Falls back to national average if no stations found in radius.

Key fields:

Field Values Meaning
predicted_direction "up", "down", "stable" Price trend direction
action "fill_now", "wait", "no_signal" Consumer-facing recommendation
confidence_label "high" (≥70), "medium" (≥40), "low" (<40) Signal strength
predicted_change_pence float Expected p/litre change over 7 days
current_avg float Average price in pence (e.g. 143.9 = 143.9p). Regional if lat/lng given, else national.

Signal weights:

Scope Signal Weight
National trend 45%
National brand_behaviour 25%
National day_of_week 20%
National price_stickiness 10%
Regional regional_momentum 50%
Regional trend 20%
Regional day_of_week 15%
Regional brand_behaviour 10%
Regional price_stickiness 5%

Signal structure (each signal in signals):

Field Type Notes
score float (-1.0 to 1.0) Negative = falling pressure, positive = rising
confidence float (0.01.0) How reliable this signal is
direction "up" / "down" / "stable" Signal direction
detail string Human-readable explanation
data_points int Number of price records used
enabled bool False if signal was skipped (missing data or coordinates)

LLM-backed prediction — separately, the nightly oil:predict command generates an oil price direction from Brent crude data and stores it in price_predictions. This feeds into AlertScoringService (Signal 4) but is not exposed directly through this endpoint. See LLM Prediction Providers.


Error Shapes

Validation error (422):

{
  "message": "The fuel type field is required.",
  "errors": {
    "fuel_type": ["The fuel type field is required."]
  }
}

Forbidden (403) — missing or invalid X-Api-Key:

{ "message": "Forbidden." }