# UK New Food Business Leads - FHRS Hygiene Ratings (`scrapersdelight/fhrs-food-hygiene-scraper`) Actor

From $0.50 per 1,000 establishments, no start fee. Newly rated and newly registered UK food businesses from the FSA FHRS register, by date window: name, address, postcode, rating, sub-scores, geocode, council. All 363 councils, 613,379 premises, no API key. No business email or phone in this source.

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

## Pricing

$0.50 / 1,000 per establishment returneds

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 New Food Business Leads — FHRS Hygiene Ratings

**Newly rated and newly registered UK food businesses, straight from the Food Standards Agency's
official register.** Set a date window, get the establishments that were rated in it — plus the
businesses that have just registered with their council and are still waiting for a first
inspection. All 363 UK councils, 613,379 premises, no API key, no start fee.

This is a **dated delta**, not a 613,000-row dump. You can have the dump if you want it
(`lookbackDays: 0`), but the point of the Actor is the 8,300-ish businesses that got rated last
month and the ones that opened last week.

***

### 📊 What one row contains

Every number below was **counted**, not estimated — over **84,835 real establishments** pulled on
**2026-09-16** from 48 councils stratified across all 12 FSA regions (13.8% of the register).
Literal `"N/A"`, `"-"` and `""` count as **not filled**.

| Field | Filled | Notes |
|---|---|---|
| `fhrsId` | **100%** | FSA's permanent id for the premises |
| `businessName` | **100%** | As published. Only 2.0% arrive in capitals, so nothing is re-cased |
| `businessType` / `businessTypeId` | **100%** | One of exactly 14 FSA categories |
| `ratingValue` | **100%** | `0`–`5` (FHRS) or `Pass` / `Improvement Required` / `Pass and Eat Safe` (Scotland) |
| `ratingStatus` | **100%** | `rated`, `awaiting-inspection`, `awaiting-publication`, `exempt` |
| `awaitingInspection` | **100%** | `true` on **6.78%** — the newest businesses in the country |
| `newRatingPending` | **100%** | `true` on 0.55% — a re-rating is already in the pipeline |
| `schemeType` | **100%** | `FHRS` (England/Wales/NI) or `FHIS` (Scotland) |
| `localAuthorityName` / `Code` / `Region` / `Website` | **100%** | Which council inspects it |
| `localAuthorityEmail` | **100%** | ⚠️ the **council's** food-safety inbox, **not** the business's |
| `localAuthorityBusinessId` | **100%** | The council's own reference — joins to council FOI data |
| `fhrsUrl` | **100%** | Link to the premises page on ratings.food.gov.uk |
| `postcode` | **97.88%** | |
| `postcodeIsPartial` | **100%** | `true` on **14.22%** of postcodes — the council published only the outward code |
| `ratingDate` | **90.49%** | `null` on the 9.51% carrying a pre-2000 placeholder |
| `address` | **86.20%** | The populated address lines, joined |
| `ratingScore` | **78.93%** | `0`–`5` as an integer; `null` for Scotland's word ratings |
| `hygieneScore` / `structuralScore` / `managementConfidenceScore` | **78.21%** | FHRS only (88.44% of FHRS rows, 0% of Scottish rows) |
| `latitude` / `longitude` | **75.53%** | Numbers, not strings. Never `0` — measured zero fake coordinates |

#### 🎯 How many rows a date window actually returns

Measured on the same sample and extrapolated to the full 613,379-row register:

| Window | % of register | UK-wide rows | Cost at $0.0005/row |
|---|---|---|---|
| Rated in last **7 days** | 0.21% | ~1,272 | **$0.64** |
| Rated in last **14 days** | 0.47% | ~2,855 | $1.43 |
| Rated in last **30 days** | 1.36% | ~8,314 | **$4.16** |
| Rated in last **90 days** | 7.08% | ~43,453 | $21.73 |
| Rated in last **365 days** | 37.43% | ~229,604 | $114.80 |
| Awaiting first inspection | 6.78% | ~41,587 | $20.79 |
| Rated 0/1/2 or "Improvement Required" | 3.21% | ~19,687 | $9.84 |
| Everything (`lookbackDays: 0`) | 100% | 613,379 | $306.69 |

Councils publish in batches, so the most recent fortnight fills in as they upload — a 7-day window
run today and re-run next week will not return the same rows.

***

### ⛔ The honest limits — read these before you buy

**1. There is no business email, and there is no business phone.**
The FHRS schema has **no email field at all**. It does have a `Phone` field: it was filled on
**0 of 84,835** records. The bulk XML export omits the element entirely. `localAuthorityEmail` is
100% filled but holds one address *per council* (5 councils in our fixtures → 5 distinct addresses)
— it is the town hall's food-safety inbox. **This is a premises list, not a contact list.** If you
need contact details you need a separate name+postcode→website enrichment step; that is a different
product and this Actor does not pretend to do it.

**2. Newly registered ≠ dated.** Businesses awaiting a first inspection carry the placeholder
`1901-01-01` (measured on 84% of them), so they cannot be filtered by date. They are delivered as a
segment (`ratingStatus: "awaiting-inspection"`) and their `ratingDate` is `null`, never 1901. If you
want a true *"appeared since my last run"* feed, use `onlyNewSinceLastRun`.

**3. Not every council publishes the awaiting-inspection segment.** Measured live on 2026-09-16:
Liverpool 1,159, Bristol 595, Cardiff 403, Sheffield 353 — but **Birmingham, Leeds and Manchester
returned zero**. If your target city is one of those, the newly-registered half of this product does
not exist there.

**4. Scotland is a different scheme.** FHIS publishes `Pass` / `Improvement Required` and **no
sub-scores whatsoever** — `hygieneScore` is `null` on 100% of Scottish rows. It is not missing data,
it is not collected. A Scottish `Pass` is a good result; it is **not** scored 0.

**5. 14.22% of postcodes are outward-code only** (`SW4`, `SE11`), across 47 of the 48 councils
sampled. They are flagged with `postcodeIsPartial` so you do not post to a postcode district.

**6. The cost of a run is fixed, not per row.** The FSA API refuses an unfiltered query, so a
UK-wide run always reads all 363 council shards (376 requests, ~480 MB) no matter how narrow your
date window is. A 1-day national window costs the same compute as a 90-day one — so prefer wider
windows, or narrow by `localAuthorities`/`regions` instead of by date.

**7. `ratingDate` is the inspection date, not the publication date.** A council can publish a
three-week-old inspection today. That is why `onlyNewSinceLastRun` exists.

***

### 🔌 How it reads the register

`api.ratings.food.gov.uk`, the FSA's own documented API, with the keyless `x-api-version: 2` header
(omit the header and the same URL returns **404**, not 403). Measured on 2026-09-16:

- **The unfiltered query is refused — by design, not by a bot wall.**
  `GET /Establishments?pageSize=10` returns **HTTP 403** with the body
  `{"Message":"This is a CPU intensive query: please use one of the documented filters in your query (e.g. filter by LocalAuthority)."}`
  No Cloudflare, no JS challenge, no cookie, no CAPTCHA. So the Actor shards by council: 363 shards,
  12 of which need a second page and one (Birmingham, 10,239 rows) a third — **376 requests** for the
  whole country.
- **Concurrency is the real constraint.** At 8 requests in flight, **8 of 16 came back HTTP 429**
  ("This is a CPU intensive query. Please either: try again later…", no `Retry-After`). At 5 in
  flight, 24 of 24 were clean; at 4, 48 of 48. The default is **4**, and a 429 pauses the whole run
  briefly rather than retrying one request into the same wall.
- **Every page is checked against its own envelope.** The API states `itemCount` per page and
  `totalCount` per council, so a short page is detected exactly instead of being shipped as a small
  council. Birmingham's three pages reconcile to 10,239 distinct ids with zero overlap.
- **Rows repeated across a page boundary are dropped before billing**, so a live insert mid-sweep
  cannot double-charge you.

***

### 💷 Pricing

**$0.0005 per establishment delivered. No run-start fee. No monthly fee.**

You are charged once per row that lands in your dataset. Establishments filtered out by your date
window, council, business type or rating are never delivered and never charged. A run that matches
nothing costs **$0.00**. Councils that could not be reached are listed in `RUN_SUMMARY` and charged
nothing.

| Typical run | Rows | Cost |
|---|---|---|
| One city, last 30 days, incl. new registrations | ~200–900 | $0.10 – $0.45 |
| Whole UK, rated in the last 7 days | ~1,272 | $0.64 |
| Whole UK, rated in the last 30 days | ~8,314 | $4.16 |
| Whole UK, everything awaiting a first inspection | ~41,587 | $20.79 |

***

### ⚙️ Recipes

**Every food business rated in the last week, nationally**

```json
{ "lookbackDays": 7, "includeNewlyRegistered": false, "maxEstablishments": 0 }
```

**Brand-new food businesses in your patch (the freshest lead in the register)**

```json
{ "lookbackDays": 0, "ratingValues": ["AwaitingInspection"], "regions": ["North West"] }
```

**Premises that just failed — the remedial-work list for a food-safety consultancy**

```json
{ "lookbackDays": 90, "ratingValues": ["0", "1", "2", "Improvement Required"], "maxEstablishments": 0 }
```

**New restaurants and takeaways only, for an EPOS or delivery-platform rep**

```json
{ "lookbackDays": 30, "businessTypes": ["Restaurant/Cafe/Canteen", "Takeaway/sandwich shop"] }
```

**A daily feed of what is genuinely new since you last looked**

```json
{ "lookbackDays": 0, "onlyNewSinceLastRun": true, "regions": ["London"], "maxEstablishments": 0 }
```

Run it on a schedule. The first run has no baseline so it returns everything in scope; every run
after that returns only what this Actor has never delivered to you before.

***

### ❓ FAQ

**Does this give me the restaurant's email address or phone number?**
No. Neither exists in the source. There is no email field, and `Phone` measured **0 filled out of
84,835** records. Anyone selling you an FHRS "email list" is enriching it from somewhere else.

**Why is `ratingDate` empty on some rows?**
Because the source put `1901-01-01` there. 9.51% of records carry a pre-2000 placeholder — almost
all of them businesses awaiting a first inspection or exempt from rating. Passing that through would
put "last inspected 1901" at the top of your oldest-first sort, so it is `null` instead.

**Why do Scottish rows have no hygiene scores?**
Scotland runs FHIS, a different scheme. It publishes `Pass` / `Improvement Required` and no
numeric sub-scores at all. Set `schemeType: "FHRS"` to exclude Scotland.

**What does a hygiene score of 0 mean?**
The best possible result. FHRS sub-scores are penalty points — **lower is better**, and a 5-star
premises typically scores 0 or 5. A `null` means not published; it never means 0.

**Can I get the whole register?**
Yes: `lookbackDays: 0` with `maxEstablishments: 0`. That is 613,379 rows, 376 requests, roughly
480 MB and several minutes — and $306.69. Most buyers want a window.

**How fresh is it?**
Each run records the API's own `extractDate` in `RUN_SUMMARY`. The register is live; councils upload
in batches, and `/Authorities` reports each council's `LastPublishedDate`. On 2026-09-16, 345 of 363
councils had published within 7 days.

**Why is it slow for a one-day window?**
Because the FSA refuses unfiltered queries, so the Actor must read every council shard and filter
locally. There is no server-side date filter — we tested `ratingDateFrom`, `ratingDate` and
`sortOptionKey=date` against the live API and all three were **silently ignored**, returning exactly
the same result as a made-up parameter.

**What happens if a council is down?**
It is retried 5 times on a fresh proxy session, then reported in `RUN_SUMMARY` under
`authoritiesUnreachable` — kept separate from `authoritiesWithNoEstablishments`, because "we could
not reach it" and "it has none" mean different things to someone working a lead list. Neither is
charged for. If more than 20% of councils fail, the run stops rather than presenting a partial
country as a complete one.

**Can I use a residential proxy?**
You can, but don't. The source has no anti-bot of any kind and a national sweep reads ~480 MB —
datacenter is both sufficient and far cheaper.

***

### ⚖️ Legal

Food hygiene ratings are **UK public-sector open data**, published by the Food Standards Agency
under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/)
via the FSA's own **documented public API**, which requires no key and no account. This Actor reads
that API exactly as documented, one council at a time, and honours its stated request limits.

**robots.txt:** checked on 2026-09-16 — `https://api.ratings.food.gov.uk/robots.txt` returns
**HTTP 404**, and so does `https://ratings.food.gov.uk/robots.txt`. Neither host publishes a
robots.txt, so there is no crawl directive to honour or breach. The Actor reads only
`api.ratings.food.gov.uk`, the host the FSA publishes for programmatic access, and never the
consumer website's search pages.

No personal data is collected: every row describes a **food business premises**, not an individual.
Some sole traders (childminders, mobile caterers) trade under their own name, which is how the FSA
publishes it. You are responsible for your own use of the data, including UK GDPR and PECR
obligations if you market to these businesses.

***

*Data source: Food Standards Agency, ratings.food.gov.uk. This Actor is not affiliated with or
endorsed by the FSA.*

# Actor input Schema

## `lookbackDays` (type: `integer`):

The main control. Returns establishments whose rating date falls in the last N days. Measured yield across the whole UK: about 1,270 in 7 days, 8,300 in 30 days, 43,500 in 90 days. Set 0 for no date filter at all (the entire register in scope - 613,379 rows). Ignored if you set explicit dates below.

## `ratedFrom` (type: `string`):

Explicit start of the rating-date window, YYYY-MM-DD. Overrides 'Rated in the last N days'. Leave empty to use the rolling window.

## `ratedTo` (type: `string`):

Explicit end of the rating-date window, YYYY-MM-DD, inclusive. Overrides 'Rated in the last N days'.

## `includeNewlyRegistered` (type: `boolean`):

These are newly registered food businesses the council has not yet inspected - the freshest lead in the register. They carry a 1901-01-01 placeholder instead of a rating date, so they cannot be date-filtered and are included as a segment (ratingStatus = awaiting-inspection). Measured 6.78% of the register, but publication is council-dependent: Liverpool 1,159, Bristol 595, and Birmingham, Leeds and Manchester zero.

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

Council name ('Birmingham'), FSA slug ('aberdeen-city'), council code ('760') or numeric id. LEAVE EMPTY to sweep all 363 UK councils (376 requests, several minutes, ~480 MB read from the API).

## `regions` (type: `array`):

Restrict to whole FSA regions. Combine with Councils to search within a region. Empty = every region.

## `businessTypes` (type: `array`):

The FSA uses exactly these 14 categories. Empty = all of them.

## `ratingValues` (type: `array`):

Keep only these ratings. England/Wales/NI use 0-5; Scotland uses Pass, Improvement Required, Pass and Eat Safe. Spacing is ignored, so 'Awaiting Inspection' also matches the FHRS spelling 'AwaitingInspection'. For the remedial-work lead list use: 0, 1, 2, Improvement Required (measured 3.21% of the register). Empty = every rating.

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

FHRS is England, Wales and Northern Ireland (0-5 ratings with sub-scores). FHIS is Scotland (Pass / Improvement Required, and it publishes no sub-scores at all).

## `onlyNewSinceLastRun` (type: `boolean`):

Monitor mode. Suppresses any establishment a previous run of this Actor already delivered to you, using a named key-value store that survives between runs. The first run has no baseline, so it delivers everything in scope. This is the only way to get a true 'newly registered' feed, because the register does not publish a registration date.

## `maxEstablishments` (type: `integer`):

Cap on rows delivered - and therefore charged. 0 means no cap. A whole-UK 30-day window returns about 8,300 rows, so raise this before a national run.

## `maxConcurrency` (type: `integer`):

Measured against the live API on 2026-09-16: 4 and 5 in flight returned 72 of 72 requests clean; 8 in flight had half come back HTTP 429 with a 'CPU intensive query' message. The Actor backs off and retries either way. Do not raise this expecting it to be faster.

## `proxyConfiguration` (type: `object`):

Datacenter is right for this source - it has no anti-bot of any kind, and a whole-UK sweep reads about 480 MB, which would be expensive over residential.

## Actor input object example

```json
{
  "lookbackDays": 30,
  "ratedFrom": "",
  "ratedTo": "",
  "includeNewlyRegistered": true,
  "localAuthorities": [
    "Liverpool",
    "Bristol",
    "Cardiff"
  ],
  "regions": [],
  "businessTypes": [],
  "ratingValues": [],
  "schemeType": "any",
  "onlyNewSinceLastRun": false,
  "maxEstablishments": 1000,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per establishment: name, type, address, postcode, hygiene rating and sub-scores, rating date, geocode, council, and a link to its page on ratings.food.gov.uk.

## `runSummary` (type: `string`):

RUN\_SUMMARY: rows read and delivered per council, the source extract date, and three separate lists that are never charged for - councils that could not be reached, councils that returned no establishments, and councils that returned fewer rows than they declared.

# 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 = {
    "lookbackDays": 30,
    "includeNewlyRegistered": true,
    "localAuthorities": [
        "Liverpool",
        "Bristol",
        "Cardiff"
    ],
    "maxEstablishments": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/fhrs-food-hygiene-scraper").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 = {
    "lookbackDays": 30,
    "includeNewlyRegistered": True,
    "localAuthorities": [
        "Liverpool",
        "Bristol",
        "Cardiff",
    ],
    "maxEstablishments": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/fhrs-food-hygiene-scraper").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 '{
  "lookbackDays": 30,
  "includeNewlyRegistered": true,
  "localAuthorities": [
    "Liverpool",
    "Bristol",
    "Cardiff"
  ],
  "maxEstablishments": 1000
}' |
apify call scrapersdelight/fhrs-food-hygiene-scraper --silent --output-dataset

```

## MCP server setup

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

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/ttR0mdsyg7HN7nT2g/builds/fZBuMaYy2dlBUNAcz/openapi.json
