# Apify Fuel Price (`davod/apify-fuel-price`) Actor

National average of petrol and diesel prices

- **URL**: https://apify.com/davod/apify-fuel-price.md
- **Developed by:** [Davod Mozafari](https://apify.com/davod) (community)
- **Categories:** Automation, Developer tools, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.00005 / actor start

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Apify Fuel Price

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://docs.python.org/3.12/)
[![Apify](https://img.shields.io/badge/platform-Apify-00A2E0?logo=apify&logoColor=white)](https://apify.com/)

**Apify Actor (Python)** for **national average** unleaded petrol and diesel prices. **The United Kingdom is supported today**; the codebase is structured so **more countries can be added** without breaking the API. Includes a **1-hour cache** (named key-value store) and a **compact JSON** row on the default dataset with **explicit currency and volume unit** so values are never ambiguous (e.g. litre vs gallon).

---

### Table of contents

- [Why this exists](#why-this-exists)
- [Features](#features)
- [Roadmap](#roadmap)
- [Requirements](#requirements)
- [Deploy on Apify](#deploy-on-apify)
- [Input](#input)
- [Output (default dataset)](#output-default-dataset)
- [Data sources](#data-sources)
- [Caching](#caching)
- [Local development](#local-development)
- [Testing](#testing)
- [Disclaimer](#disclaimer)
- [License](#license)
- [References](#references)

---

### Why this exists

Fuel price averages move often. This Actor gives you a **small, stable JSON** payload suitable for dashboards, alerts, or downstream pipelines—without running a browser—by reading the same public pages and sheet endpoints a normal visitor would hit.

---

### Features

- **Multi-country direction:** built to support **more countries over time**; **only `uk` is wired today** (see [`SUPPORTED_COUNTRIES`](src/config.py) and fetchers under `src/fetchers/`).
- **Pricing contract:** each supported country has its own ISO **`currency`** and **`volumeUnit`**; **`petrol`** and **`diesel`** are always that currency's **major unit** per that volume (no cents/pence field—local quoting is normalized in code). **UK today:** sources use pence/litre; output is pounds per litre with `currency: GBP`.
- **Case-insensitive input:** `uk`, `UK`, `Uk`, etc. (validated in [`.actor/input_schema.json`](.actor/input_schema.json)).
- **Upstream HTTP:** each GET uses a **10 second** timeout ([`src/config.py`](src/config.py)); UK fallback **fetches both gviz endpoints in parallel** so each request is still bounded by that timeout.
- **Resilient fetch:** primary HTML from [PetrolPrices.co.uk](https://petrolprices.co.uk/uk-fuel-prices-live.php), fallback to public **Google Visualization** JSON used by [PetrolPrices.com](https://www.petrolprices.com/latest-fuel-price-data-across-the-uk/).
- **Cache:** 1-hour TTL in a **named** key-value store (see [Caching](#caching)); **no cache metadata** in the dataset row (TTL uses `fetchedAt` in KV only).
- **Docker:** [`Dockerfile`](Dockerfile) based on [`apify/actor-python:3.12`](https://docs.apify.com/sdk/python/docs/overview) for parity with Apify Cloud.

---

### Roadmap

- **Near term:** add more `country` values, fetchers, and [`COUNTRY_OUTPUT_PRICING`](src/config.py) entries (`currency` + `volumeUnit`); extend `_parsed_to_output_record` in [`src/services/fuel_service.py`](src/services/fuel_service.py) so each locale normalizes to major currency per volume.
- **Input schema:** will gain new `enum` / `pattern` values as countries ship; today it only accepts UK codes.

---

### Requirements

| Context | Notes |
|---------|--------|
| **Apify Cloud** | Matches the Dockerfile (Python 3.12). |
| **Local `python -m src.main`** | **Python 3.10+** recommended (Apify SDK / Crawlee expectations). |
| **Tests** | Parser tests are offline. **Live HTTP** tests need network (see [Testing](#testing)). |

Optional: [Apify CLI](https://docs.apify.com/cli) for `apify run` / deploy workflows.

---

### Deploy on Apify

1. Push this repository to GitHub (or upload the project).
2. In [Apify Console](https://console.apify.com/), create an Actor from the Git repository (or `apify push` if you use the CLI).
3. Set **build** to use the root [`Dockerfile`](Dockerfile) and run with default memory as needed.

After the first run, inspect the **default dataset** for the JSON row described below.

---

### Input

Defined in [`.actor/input_schema.json`](.actor/input_schema.json) (validated by Apify before the run starts).

| Field | Type | Description |
|-------|------|-------------|
| `country` | string | **UK today**; more countries planned. Case-insensitive (`uk`, `UK`, …). Pattern: `^[Uu][Kk]$`. |

---

### Output (default dataset)

Apify Console **Output** tab and API run `output` links are driven by [`.actor/output_schema.json`](.actor/output_schema.json); dataset field metadata and the results table use [`.actor/dataset_schema.json`](.actor/dataset_schema.json) ([output schema](https://docs.apify.com/platform/actors/development/actor-definition/output-schema), [dataset schema](https://docs.apify.com/platform/actors/development/actor-definition/dataset-schema)).

Each **successful** run appends **one object** to the **default dataset**.

| Field | Type | Description |
|-------|------|-------------|
| `country` | string | Normalized lowercase code (`uk` today). |
| `petrol` | number | National average unleaded: **major unit** of `currency` per `volumeUnit`. |
| `diesel` | number | National average diesel, same units as `petrol`. |
| `currency` | string | ISO 4217 code for how to read the two prices (e.g. UK **`GBP`**, future regions their own code). |
| `volumeUnit` | string | Volume basis for the “per” price (UK: **`litre`**). |
| `lastUpdate` | string | ISO-8601 UTC: upstream “last updated” when parseable, otherwise the fetch time. |

**Convention:** `petrol` and `diesel` use the **major ISO 4217 unit** of `currency` per `volumeUnit` (e.g. GBP as pounds, USD as dollars—not pence or cents as the numeric scale). Each country's pipeline converts local quoting to that scale. **UK:** public sources use pence/litre; the Actor outputs **pounds per litre** with `currency: GBP`.

**Caching:** TTL bookkeeping uses a `fetchedAt` field **only inside the named key-value store**, not in the dataset payload.

**Errors:** On failure you may still get **one** dataset item with `error`, `message`, and `country`, and the run is marked failed.

---

### Data sources

1. **Primary:** [PetrolPrices.co.uk — UK Fuel Prices Live](https://petrolprices.co.uk/uk-fuel-prices-live.php) (HTML; includes “Data feed last updated”).
2. **Fallback:** Public `gviz/tq` JSON for the same spreadsheet surfaced on [PetrolPrices.com — Latest fuel price data](https://www.petrolprices.com/latest-fuel-price-data-across-the-uk/).

No headless browser in v1. If both paths break, consider a Playwright-based fallback in a future version.

---

### Caching

| Item | Value |
|------|--------|
| **Store name** | `fuel-price-actor-cache` (see [`src/config.py`](src/config.py)) |
| **Key** | `fuel_avg_<country>` (e.g. `fuel_avg_uk`) |
| **TTL** | 1 hour, enforced in [`src/cache.py`](src/cache.py) |

The named store is shared across runs for your Apify account (unlike an ephemeral default store per run).

---

### Local development

```bash
python -m venv .venv
source .venv/bin/activate   ## Windows: .venv\Scripts\activate
pip install -r requirements.txt
````

Create `storage/key_value_stores/default/INPUT.json`:

```json
{ "country": "UK" }
```

Run the Actor (Python **3.10+** locally; otherwise use Apify Cloud / Docker):

```bash
python -m src.main
```

Results are written under `storage/datasets/default/`. To reset local storages when using the Apify CLI:

```bash
apify run --purge
```

***

### Testing

```bash
pytest -v
```

- **Default run** includes **live HTTP** tests in [`tests/test_integration_live.py`](tests/test_integration_live.py) (marked `integration`, **network required**).
- **Offline / CI:** GitHub Actions runs `pytest -v -m "not integration"`. Locally you can do the same if you are offline.

```bash
pytest -v -m "not integration"
```

Print live responses:

```bash
pytest -v -s tests/test_integration_live.py
```

Print the **exact dataset JSON shape** (fixture-based, offline):

```bash
pytest -v -s tests/test_actor_output_contract.py
```

***

### Disclaimer

This project **reads publicly available** pages and sheet endpoints. You are responsible for complying with **terms of use**, **robots.txt**, and **applicable law** where you run the Actor. The software is provided **as-is**; average prices and upstream markup can change at any time. This repository is **not** affiliated with PetrolPrices.co.uk, PetrolPrices.com, or any other websites.

***

### License

This project is released under the **[MIT License](https://opensource.org/licenses/MIT)**. The full legal text is in [`LICENSE`](LICENSE).

***

### References

- [Apify SDK for Python — Overview](https://docs.apify.com/sdk/python/docs/overview)
- [Actor input schema specification](https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1)

# Actor input Schema

## `country` (type: `string`):

Country code. United Kingdom is supported today; others will follow. Case-insensitive: uk, UK, Uk, etc.

## Actor input object example

```json
{
  "country": "uk"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Items from the default dataset (JSON): one row with petrol/diesel averages, or an error row with error, message, and country.

# 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 = {
    "country": "uk"
};

// Run the Actor and wait for it to finish
const run = await client.actor("davod/apify-fuel-price").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 = { "country": "uk" }

# Run the Actor and wait for it to finish
run = client.actor("davod/apify-fuel-price").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "country": "uk"
}' |
apify call davod/apify-fuel-price --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=davod/apify-fuel-price",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Apify Fuel Price",
        "description": "National average of petrol and diesel prices",
        "version": "0.0",
        "x-build-id": "3p8ZPKfKZKKxwvDku"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/davod~apify-fuel-price/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-davod-apify-fuel-price",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/davod~apify-fuel-price/runs": {
            "post": {
                "operationId": "runs-sync-davod-apify-fuel-price",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/davod~apify-fuel-price/run-sync": {
            "post": {
                "operationId": "run-sync-davod-apify-fuel-price",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "country"
                ],
                "properties": {
                    "country": {
                        "title": "Country",
                        "pattern": "^[Uu][Kk]$",
                        "type": "string",
                        "description": "Country code. United Kingdom is supported today; others will follow. Case-insensitive: uk, UK, Uk, etc.",
                        "default": "uk"
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
