# UK Food Hygiene Rating Downgrade Watchdog (`hllerdgn80/uk-food-hygiene-rating-watchdog`) Actor

Tracks UK Food Standards Agency hygiene ratings run over run and flags actual changes: rating downgrades, upgrades, and overdue re-inspections. Keyless official FSA API - no other Store Actor compares ratings across time.

- **URL**: https://apify.com/hllerdgn80/uk-food-hygiene-rating-watchdog.md
- **Developed by:** [Halil Erdogan](https://apify.com/hllerdgn80) (community)
- **Stats:** 2 total users, 1 monthly users, 33.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 results

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## UK Food Hygiene Rating Downgrade Watchdog

Tracks UK Food Standards Agency (FSA) hygiene ratings **run over run** and
flags what actually changed, instead of dumping a static snapshot.

Every other Food Hygiene Rating Scheme (FHRS/FHIS) Actor on the Apify Store
(checked 27 Sep 2026 - eight competing scrapers, none above 4 total users)
does the same thing: pull the current ratings for an area and hand them
back as one list. None of them remember what the rating was *last time*.
This Actor's whole point is that comparison.

### What it does

On every run it:

1. Loads the last-seen rating for every establishment it has ever checked
   from its own private key-value store.
2. Queries the official, public, **keyless** FSA API
   (`api.ratings.food.gov.uk`) for every local authority, address/postcode,
   or specific establishment (FHRSID) you ask it to watch.
3. Classifies each establishment against its saved state:
   - **`downgrade`** - the numeric FHRS rating fell (e.g. 5 → 1). The
     headline alert.
   - **`upgrade`** - the rating rose.
   - **`re_inspected_no_change`** - a new inspection happened but the
     rating stayed the same.
   - **`overdue_for_reinspection`** - not already top-rated (FHRS 5 or
     FHIS "Pass") and no new inspection in longer than your configured
     threshold. No other FSA Actor surfaces this at all.
   - **`new_establishment`** - the first time this business has been seen.
   - **`unchanged`** - nothing new.
4. Pushes one row per establishment (or only the changed/alert rows, if you
   ask for that) and saves the new state for next run.

### Why this is different

A one-off scrape of a council's food ratings is a commodity - eight Actors
already do it, badly, at near-zero adoption. A **downgrade watchdog** that
tells a resident, journalist, franchise auditor, or insurer "this specific
restaurant just dropped from 5 to 1" - or "this takeaway hasn't been
re-inspected in three years and isn't top-rated" - is a genuinely different
product built on the exact same free data.

### Input

| Field | Type | Description |
|---|---|---|
| `localAuthorities` | array of strings | Council names to watch, e.g. `"Manchester"`. |
| `addressesOrPostcodes` | array of strings | Free-text address/postcode searches, e.g. `"SW1A 1AA"`. |
| `watchFhrsids` | array of numbers/strings | Specific establishment IDs to track individually. |
| `maxResultsPerArea` | integer | Cap per search area (default 200, max 2000). |
| `overdueMonths` | integer | Re-inspection overdue threshold in months (default 18). |
| `onlyAlerts` | boolean | Only push `downgrade` + `overdue_for_reinspection` rows. |
| `onlyChangedRecords` | boolean | Skip anything unchanged since the last run. |

At least one of `localAuthorities`, `addressesOrPostcodes`, or
`watchFhrsids` is required.

### Output (one dataset row per establishment)

```json
{
  "fhrsid": 1821479,
  "business_name": "Example Takeaway",
  "business_type": "Takeaway/sandwich shop",
  "address": "12 High Street, Manchester",
  "postcode": "M1 1AA",
  "local_authority": "Manchester",
  "scheme_type": "FHRS",
  "rating_value": "1",
  "rating_date": "2026-09-01T00:00:00",
  "months_since_inspection": 0.9,
  "previous_rating_value": "5",
  "previous_rating_date": "2025-02-17T00:00:00",
  "rating_delta": -4,
  "event_type": "downgrade",
  "overdue_flag": false,
  "fsa_url": "https://ratings.food.gov.uk/business/en-GB/1821479"
}
```

### Data source

[Food Standards Agency Food Hygiene Rating Scheme API](https://api.ratings.food.gov.uk)

- official UK government open data, public and keyless, covering FHRS
  (England/Wales/NI, 0-5 scale) and FHIS (Scotland, Pass/Improvement
  Required) establishments. No scraping, no authentication, no terms-of-use
  workaround: every request hits the documented public endpoint.

### Pricing model

Pay-per-event (`hygiene-record-detected`), charged once per dataset row
pushed. A search area that fails to respond charges nothing and is
reported separately in the run's key-value store under `RUN_STATS`.

# Actor input Schema

## `localAuthorities` (type: `array`):

Council/local-authority names to check, e.g. "Manchester" or "Westminster". Every food business currently registered with that authority is checked each run. Matched against the FSA's own authority list (exact match first, then contains).

## `addressesOrPostcodes` (type: `array`):

Free-text addresses or postcodes to search, e.g. "SW1A 1AA" or "Camden High Street". Uses the FSA API's own address search, same as the public ratings.food.gov.uk site.

## `watchFhrsids` (type: `array`):

Exact FSA establishment IDs (FHRSID, visible in a ratings.food.gov.uk business URL) to track individually run after run, regardless of area, e.g. for a specific restaurant or franchise site.

## `maxResultsPerArea` (type: `integer`):

Upper limit on how many establishments to pull back for each local-authority or address search area, to keep large council areas (some cover 2,000+ businesses) from ballooning a single run.

## `overdueMonths` (type: `integer`):

An establishment that is not already top-rated (FHRS 5, or FHIS 'Pass') and has had no new inspection in at least this many months is flagged as 'overdue\_for\_reinspection' - the group most worth a fresh check, and a status no other FSA-data scraper surfaces.

## `onlyAlerts` (type: `boolean`):

If enabled, only push 'downgrade' and 'overdue\_for\_reinspection' rows - the two event types someone monitoring food safety actually needs to act on. Overrides 'Only return changed records' when both are set.

## `onlyChangedRecords` (type: `boolean`):

If enabled, skip establishments whose rating and inspection date have not changed since this Actor's last run against the same area. Leave off for a full snapshot on the first run (nothing has 'changed' yet, so everything is new\_establishment).

## Actor input object example

```json
{
  "localAuthorities": [
    "Westminster"
  ],
  "addressesOrPostcodes": [],
  "watchFhrsids": [],
  "maxResultsPerArea": 200,
  "overdueMonths": 18,
  "onlyAlerts": false,
  "onlyChangedRecords": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("hllerdgn80/uk-food-hygiene-rating-watchdog").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("hllerdgn80/uk-food-hygiene-rating-watchdog").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call hllerdgn80/uk-food-hygiene-rating-watchdog --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hllerdgn80/uk-food-hygiene-rating-watchdog"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/5s7fO3la1fNuVR0x1/builds/5dYTDpmXGEMtoYPrb/openapi.json
