8.6 KiB
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.1–50) |
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 A–Z |
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_penceis the raw integer (e.g.14390= 143.90p).priceis 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_priceare in pence as floats (e.g.141.2= 141.2p).unique_searchersis 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.0–1.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." }