# UK Food Hygiene Ratings Scraper (FHRS / FSA Data) (`steadfetch/uk-food-hygiene-monitor`) Actor

Extract UK food establishment hygiene ratings from the Food Standards Agency open-data API: rating, scheme, local authority and business details. For food-safety due diligence, site checks and compliance dashboards. Filter by authority and minimum rating. No browser, no login.

- **URL**: https://apify.com/steadfetch/uk-food-hygiene-monitor.md
- **Developed by:** [Steadfetch](https://apify.com/steadfetch) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 establishment delivereds

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

## steadfetch/uk-food-hygiene-monitor

Extract UK food establishment hygiene ratings from the official [Food Standards Agency open data API](https://ratings.food.gov.uk/open-data). Structured JSON, no browser, no login, no CAPTCHA.

### What it does

The Actor queries the FSA `/Establishments` API (version 2) and returns one dataset item per food business, with its current rating, rating date, business type, published address, local authority and coordinates.

- Filter by **local authority** (one or more names, comma-separated).
- Filter by **minimum rating** (0-5).
- Choose the **FHRS** (England, Wales, Northern Ireland) or **FHIS** (Scotland) scheme.
- Cap the number of results with `maxResults`.

Every item is validated against the schema in `.actor/dataset_schema.json` before it reaches the dataset. With `strictSchema: true` (the default) an invalid item fails the run instead of being pushed.

The FSA publishes this data under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). The API is free and needs no registration. The Actor does not scrape the ratings website and never returns personal contact data: it only extracts business premises information that the FSA already publishes.

### Minimum input

```json
{ "region": "Camden", "maxResults": 10 }
```

All fields are optional: an empty `region` queries every local authority, bounded by `maxResults` (default 100 — a useful first sample that finishes in seconds and stays well inside the Apify free plan; raise it for bulk pulls). `minRating` (0-5) keeps only numeric FHRS ratings at or above the value; `schemeType` selects `FHRS` (default, England/Wales/NI) or `FHIS` (Scotland).

### Output fields

- `fhrs_id` — establishment identifier.
- `business_name`, `business_type` — premises name and type, e.g. `Restaurant/Cafe/Canteen`.
- `rating_value` — published rating: `0`-`5`, `Pass`, `Improvement Required`, `AwaitingInspection`, `AwaitingPublication` or `Exempt`.
- `rating_date` — date of the rating.
- `address_line`, `postal_code`, `local_authority_name` — location as published by the FSA.
- `latitude`, `longitude` — premises coordinates.
- `url`, `title` and `scrapedAt` — public FSA ratings page and envelope. Business premises only: no individual contact data.

### Example output

```json
{
    "url": "https://ratings.food.gov.uk/business/1824267",
    "title": "1 REBEL",
    "scrapedAt": "2026-10-19T18:00:00.000Z",
    "fhrs_id": "1824267",
    "business_name": "1 REBEL",
    "business_type": "Restaurant/Cafe/Canteen",
    "address_line": "1 REBEL, Lancing Street, London",
    "postal_code": "NW1 1NA",
    "local_authority_name": "Camden",
    "rating_value": "5",
    "rating_date": "2026-03-18",
    "latitude": 51.528421,
    "longitude": -0.1314354
}
```

### Input

```json
{
    "region": "Camden",
    "minRating": 4,
    "schemeType": "FHRS",
    "maxResults": 1000
}
```

| Field | Type | Description |
| --- | --- | --- |
| `region` | string | One or more FSA local-authority names, comma-separated. Empty queries every authority (bounded by `maxResults`). |
| `minRating` | integer 0-5 | Exclude lower numeric ratings. Non-numeric values such as `AwaitingInspection` are excluded when set. Applies to FHRS only; FHIS uses `Pass` / `Improvement Required`, so omit it for FHIS. |
| `schemeType` | `FHRS` or `FHIS` | Default `FHRS`. Scottish authorities publish FHIS; the rest of the UK publishes FHRS. |
| `maxResults` | integer | Default 100, maximum 20000. |
| `strictSchema` | boolean | Default `true`; fail the run on schema violations. |

### Project structure

```
.actor/actor.json          Actor definition (payPerEvent, dataset schema, output schema)
.actor/input_schema.json   INPUT options (region, minRating, schemeType, maxResults)
.actor/dataset_schema.json Item JSON Schema (fields) + table view for the Store
.actor/pay_per_event.json  PPE pricing (two synthetic platform events)
src/fetch.ts               FSA HTTP client: pagination, timeouts, retry/backoff
src/extract.ts             Pure parsing and filtering (fixture-tested, no network)
src/main.ts                Entry point: fetch, validate, push
src/validate-output.ts     Ajv validator shared by the run and the tests
test/                      Offline fixtures and Vitest suites
```

### Pay-per-event pricing

The Actor uses Apify **pay-per-event** pricing with the two synthetic platform events:

| Event | Charged when | Price |
| --- | --- | --- |
| `apify-actor-start` | a run starts (synthetic, automatic) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute) |
| `apify-default-dataset-item` | an establishment is saved to the dataset (synthetic, automatic) | 0.004 USD per result = **4.00 USD per 1,000 results** |

There is no custom per-item event and no `Actor.charge()` call: the platform already charges `apify-default-dataset-item` per pushed item, so adding another event would bill users twice.

The price sits in the middle of the direct-competitor band (2.5-8 USD per 1,000) and inside Steadfetch's 1-10 USD band.

**Measured cost per 1,000 results:** a local run over the live FSA API (`region: "Camden"`, `maxResults: 1000`) delivered 1,000 schema-valid items in **6.8 seconds** using the 256 MB minimum memory allocation. At Apify's $0.20 per compute unit (1 CU = 1 GB-hour), that is 0.00047 CU = **$0.0001 per 1,000 results**; even with a conservative allowance for startup, dataset storage and data transfer the cost stays **below $0.001 per 1,000 results**. Margin over measured cost is above 99%.

**Works on the Apify free plan** — about 1,249 establishments for the $5 monthly credit ((5.00 USD − 0.0002 USD run-start at the default 4 GB memory) ÷ 0.004 USD per result).

### Development

```bash
npm install
npm test                # lint + typecheck + offline Vitest suites (no credit)
npm run publish:check   # Steadfetch publish gate
npm run schemas:validate
npm run smoke            # local Apify run (no platform credit)
```

The test suite runs entirely against committed JSON fixtures captured from the real FSA API on 2026-10-19. A live run only needs the public API; no token is required.

### FAQ

**Do I need an FSA account or API key?**
No. The FSA open data API is free and needs no registration.

**Does it need a browser?**
No. The FSA returns JSON, so the Actor uses plain HTTP. That is roughly 10x cheaper than a browser-based Actor.

**Does it return personal data?**
No. The Actor only returns business premises fields already published by the FSA. It does not return phone numbers, email addresses or any individual's data.

**When am I charged?**
Once per run start (`apify-actor-start`) and once per valid result saved to the dataset (`apify-default-dataset-item`). Failed requests, retries and empty pages are not charged per result.

**Is the output schema stable?**
Yes. New fields are additive and the required fields in `.actor/dataset_schema.json` do not change.

### Maintenance

This Actor is **tested daily**: automated runs check that the FSA API is reachable and that the output still matches the published schema. Failures are reported as issues in the Steadfetch Actors repository.

# Actor input Schema

## `region` (type: `string`):

One or more FSA local-authority names, comma-separated (for example "Camden" or "Bristol, Bath and North East Somerset"). Leave empty to query every authority (scoped by maxResults).

## `minRating` (type: `integer`):

Only return establishments whose numeric rating is greater than or equal to this value (0-5). Non-numeric ratings such as "AwaitingInspection" are excluded when this filter is set.

## `schemeType` (type: `string`):

FHRS (Food Hygiene Rating Scheme, England/Wales/Northern Ireland) or FHIS (Food Hygiene Information Scheme, Scotland).

## `maxResults` (type: `integer`):

Maximum number of validated establishments to save to the dataset. Default 100 keeps the first run quick and cheap on the Apify free plan; raise it for bulk pulls (maximum 20,000).

## `strictSchema` (type: `boolean`):

Fail the run when an item does not match the OUTPUT schema. Keep enabled: Steadfetch never publishes Actors without output-schema validation.

## Actor input object example

```json
{
  "region": "Camden",
  "minRating": 4,
  "schemeType": "FHRS",
  "maxResults": 10,
  "strictSchema": true
}
```

# Actor output Schema

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

Dataset items validated against the item schema in dataset_schema.json (fields).

# 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 = {
    "region": "Camden",
    "maxResults": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/uk-food-hygiene-monitor").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 = {
    "region": "Camden",
    "maxResults": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/uk-food-hygiene-monitor").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 '{
  "region": "Camden",
  "maxResults": 10
}' |
apify call steadfetch/uk-food-hygiene-monitor --silent --output-dataset

```

## MCP server setup

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

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/tfRUT9MS3lTyph6rJ/builds/VxNbYH8txdp1BwdXo/openapi.json
