# UK Food Hygiene Prospect Finder — FSA Ratings (`nexgenwatch/uk-food-hygiene-prospect-finder`) Actor

Prospecting lists of UK food businesses from the FSA's own public ratings API: rating, scores, address and authority contact per establishment.

- **URL**: https://apify.com/nexgenwatch/uk-food-hygiene-prospect-finder.md
- **Developed by:** [NexGen Watch](https://apify.com/nexgenwatch) (community)
- **Categories:** Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $67.00 / 1,000 establishment records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## UK Food Hygiene Prospect Finder

Build a **prospecting list of UK food businesses** from the Food Standards Agency's own public
API — filtered by local authority, business type, name and **hygiene rating** — with a link to
the FSA's page for every establishment.

Built for food-safety consultants, pest control, EHO training, equipment suppliers and anyone
whose buyer is a business that needs to improve its rating.

### The filter you came for does not work on the server. That is the point of this actor.

The FSA API accepts a `ratingKey` parameter. **It is silently ignored.** Measured 2026-07-31
against a control of 1,477 establishments in one authority:

| query | establishments returned |
|---|---|
| no rating filter (control) | 1,477 |
| `ratingKey=fhrs_5_en-gb` (valid) | **1,477** |
| `ratingKey=fhrs_0_en-gb` (valid) | **1,477** |
| `ratingKey=fhrs_9_en-gb` (invented) | **1,477** |
| `ratingKey=banana` (garbage) | **1,477** |
| `ratingKey=` (empty) | **1,477** |

Every value returns the unfiltered set, and the API never says so. A tool that passed this
through would answer *"here are the 1-rated establishments in your area"* with **every**
establishment in the authority, five-star ones included — the exact opposite of a prospect
list.

**So this actor never sends it.** Rating filtering runs here, over each record's own published
`RatingValue`, where it provably works.

### Two more traps, both measured

#### An unfiltered query is refused — and the 403 is not a permission problem

```
GET /Establishments?pageSize=1&pageNumber=1
-> 403 {"Message":"This is a CPU intensive query: please use one of the
        documented filters in your query (e.g. filter by LocalAuthority)."}
```

Passing that on would read as *"the register is closed to you"*. It isn't — a filter is
mandatory. The request is refused **before it is sent**, with the reason.

#### An unrecognised value returns zero, not an error

```
localAuthorityId=99999  -> HTTP 200, totalCount 0
businessTypeId=99999    -> HTTP 200, totalCount 0
```

A typo therefore answers *"there are no food businesses there"*. Every value is validated
against the API's own enumerations first — **363 local authorities, 14 business types, 11
rating values** — and an unknown one is refused, not guessed.

**An ambiguous authority name is refused too.** `Cambridge` is carried by three authorities —
*Cambridge City*, *East Cambridgeshire* and *South Cambridgeshire*. Matching on a prefix would
resolve silently to the city, and a buyer who meant the county would get a bill for the wrong
list with no way to see it happened. Names are matched as substrings and anything but a single
hit is refused, with the candidates named.

### Scotland is different, and it is not fudged

Scottish establishments use FHIS, which publishes **words** — `Pass`, `Improvement Required` —
not numbers. `ratingAtOrBelow` cannot rank those, so it **excludes** them rather than guessing
a score, and every result reports how many were excluded that way.

### Filters

| filter | what it does |
|---|---|
| `localAuthorityName` / `localAuthorityId` | one of 363 authorities; ambiguous names refused |
| `businessTypeName` / `businessTypeId` | one of 14 published types |
| `businessName` | free text, matched by the FSA against establishment names |
| `ratingValues` | keep only these ratings — **applied here, not on the server** |
| `ratingAtOrBelow` | numeric ratings at or below N — the usual prospecting filter |
| `maxResults` | how many establishments to return — **also your cost ceiling** |

### Output

One record per establishment: name, business type, full address, postcode, rating and rating
date, the three component scores (hygiene, structural, management), local authority plus its
**contact email and website**, coordinates, and a link to the FSA's page. Plus a
`search_summary` naming every trap and a `source_receipt`.

### Nothing is inferred

Every field is carried verbatim from the FSA. This actor assigns no score, ranks nobody, and
states **no view** on any business's food safety beyond the rating the FSA itself published. A
low rating is a snapshot from an inspection on a stated date, not a current condition.

### Reading policy

`api.ratings.food.gov.uk` serves no robots.txt (404) and `x-api-version: 2` is sent on every
call. The actor paces itself at **2 requests/second** on an official public API. **The rate is
not an input.**

### Pricing

| Event | Price |
|---|---|
| Actor start (`apify-actor-start`) | $0.02, charged once per run by the platform |
| Establishment record (`establishment-result`) | **$0.10 each** (FREE tier) |

Tiers per record: $0.10 / $0.09 / $0.08 / $0.067 / $0.067 / $0.067.

**You are billed per record returned, so `maxResults` is your cost ceiling** — 100 results is
$10.00 plus the $0.02 start.

**A zero-result query costs nothing** beyond the start fee, and says so.

**Every refusal costs nothing:** an unfiltered query, an unknown or ambiguous authority, an
unknown business type, an unknown rating value. **A source failure fails the run loudly and
charges nothing** — it never reports a broken response as "no results".

# Actor input Schema

## `localAuthorityName` (type: `string`):

A UK local authority by name, e.g. Cambridge City. 363 are published. An ambiguous name is refused rather than silently resolved to one of them.

## `localAuthorityId` (type: `integer`):

The FSA's own numeric authority ID. Takes precedence over the name. An unrecognised ID is refused — the API answers one with 200 and zero results, which reads as 'no food businesses here'.

## `businessTypeName` (type: `string`):

One of the 14 published types, e.g. Restaurant/Cafe/Canteen, Takeaway/sandwich shop, Retailers - other. A name matching more than one type is refused.

## `businessTypeId` (type: `integer`):

The FSA's own numeric business-type ID. Takes precedence over the name.

## `businessName` (type: `string`):

Free text matched by the FSA against establishment names.

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

Keep only these ratings, e.g. 0, 1, 2 for England/Wales/NI, or Pass / Improvement Required for Scotland. IMPORTANT: the FSA API's own rating filter is silently ignored — measured, every value including invented ones returns the unfiltered set — so this filter is applied here, over each record's published rating.

## `ratingAtOrBelow` (type: `integer`):

Keep only numeric ratings at or below this value — the usual prospecting filter. Scottish FHIS results carry words, not numbers, so they are excluded rather than guessed at, and the count of exclusions is reported.

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

You are billed per establishment returned, so this is also your cost ceiling: at $0.10 per record, 100 results is $10.00 plus the $0.02 start fee.

## Actor input object example

```json
{
  "localAuthorityName": "Cambridge City",
  "maxResults": 25
}
```

# 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 = {
    "localAuthorityName": "Cambridge City",
    "maxResults": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("nexgenwatch/uk-food-hygiene-prospect-finder").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 = {
    "localAuthorityName": "Cambridge City",
    "maxResults": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("nexgenwatch/uk-food-hygiene-prospect-finder").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 '{
  "localAuthorityName": "Cambridge City",
  "maxResults": 25
}' |
apify call nexgenwatch/uk-food-hygiene-prospect-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nexgenwatch/uk-food-hygiene-prospect-finder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/DVTjufF0IIJkWHDzD/builds/3lLkYyckb941tB2WR/openapi.json
