UK Food Hygiene Prospect Finder — FSA Ratings avatar

UK Food Hygiene Prospect Finder — FSA Ratings

Pricing

from $67.00 / 1,000 establishment records

Go to Apify Store
UK Food Hygiene Prospect Finder — FSA Ratings

UK Food Hygiene Prospect Finder — FSA Ratings

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

Pricing

from $67.00 / 1,000 establishment records

Rating

0.0

(0)

Developer

NexGen Watch

NexGen Watch

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

🔎 UK Food Hygiene Prospect Finder — FSA Ratings

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.

Output is one establishment row per result; billing is pay-per-event, the value event being one establishment record (a $0.02 start fee per run, then $0.10 per establishment record). Source: api.ratings.food.gov.uk.

No login, no API key and no CAPTCHA solving are involved: the source is read logged-out.

📊 Sample Output

UK Food Hygiene Prospect Finder — FSA Ratings sample output — a table of real establishment record rows (business_name, business_type, address, local_authority) from run bztzbXdHh5fSaREgS on build 0.1.18

Real rows from run bztzbXdHh5fSaREgS on build 0.1.18 (2026-09-17), the same input as the Quick start below — every value is as the source published it (emails masked, long text shortened):

business_namebusiness_typeaddresslocal_authoritylocal_authority_emaillocal_authority_website
196Restaurant/Cafe/Canteen196 Mill Road, Cambridge, Cambridgeshire, CB1 3NFCambridge City[email on record]http://www.cambridge.gov.uk
44 Russell Street (Respite Service)Hotel/bed & breakfast/guest house44 Russell Street, Cambridge, Cambridgeshire, CB2 1HTCambridge City[email on record]http://www.cambridge.gov.uk
5 BlendsRestaurant/Cafe/Canteen42 Mill Road, Cambridge, Cambridgeshire, CB1 2ADCambridge City[email on record]http://www.cambridge.gov.uk
5 Chapel Street B&BHotel/bed & breakfast/guest house5 Chapel Street, Cambridge, Cambridgeshire, CB4 1DYCambridge City[email on record]http://www.cambridge.gov.uk
7Bone CambridgeTakeaway/sandwich shopUnit 3 To 4 Restwell House Coldhams Road, Cambridge, Cambridgeshire, CCambridge City[email on record]http://www.cambridge.gov.uk
92 @ Young StreetRestaurant/Cafe/CanteenThe Health Building Young Street, Cambridge, Cambridgeshire, CB1 2LZCambridge City[email on record]http://www.cambridge.gov.uk
A WALLER & SONManufacturers/packers15 Victoria Avenue, Cambridge, Cambridgeshire, CB4 1EGCambridge City[email on record]http://www.cambridge.gov.uk
A&M Pub Services (The Ship)Pub/bar/nightclubThe Ship Northfield Avenue, Cambridge, Cambridgeshire, CB4 2LGCambridge City[email on record]http://www.cambridge.gov.uk

The run finished with the status message: NORMAL: returned=10 scanned=100 server_total=1486 | establishment-result charged=10 (one per returned establishment)

✅ What you get

Each row is flat JSON with these fields (from the dataset schema and the sample run; a field the source does not publish for a given row is null):

  • record_type (string) — e.g. establishment
  • fhrs_id (integer/null) — e.g. 1472773
  • business_name (string/null) — e.g. 196
  • business_type (string/null) — e.g. Restaurant/Cafe/Canteen
  • business_type_id (integer/null) — e.g. 1
  • address (string/null) — e.g. 196 Mill Road, Cambridge, Cambridgeshire, CB1 3NF
  • postcode (string/null) — e.g. CB1 3NF
  • local_authority (string/null) — e.g. Cambridge City
  • local_authority_id — e.g. 027
  • local_authority_email (string/null) — e.g. [email on record]
  • local_authority_website (string/null) — e.g. http://www.cambridge.gov.uk
  • rating_value (string/null) — e.g. 5
  • rating_is_numeric (boolean/null) — e.g. True
  • rating_date (string/null) — e.g. 2026-02-25T00:00:00
  • scheme_type (string/null) — e.g. FHRS
  • new_rating_pending (boolean/null) — e.g. False
  • hygiene_score (integer/null) — e.g. 5
  • structural_score (integer/null) — e.g. 5
  • management_score (integer/null) — e.g. 0
  • latitude (string/null) — e.g. 52.1976012
  • longitude (string/null) — e.g. 0.1440631
  • source_url (string/null) — e.g. https://ratings.food.gov.uk/business/en-GB/1472773
  • source_is — e.g. the Food Standards Agency's own public ratings API
  • report_id (string/null) — e.g. 7100bec1c91c7b44
  • establishments_returned (integer/null) — null in every sample row
  • establishments_scanned (integer/null) — null in every sample row
  • establishments_matching_the_server_side_filters (integer/null) — null in every sample row
  • coverage_caveat (string/null) — null in every sample row
  • terminal (string/null) — null in every sample row
  • refused (boolean/null) — null in every sample row
  • reason (string/null) — null in every sample row

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.

Every run also writes a RUN_RECEIPT record to its key-value store with the source checks it made and the counts it charged — diagnostics never land in the paid dataset.

⚙️ Sample inputs

1. Quick start — the Store example (this is what the sample above came from)

{
"localAuthorityName": "Cambridge City",
"maxResults": 10
}

The sample run charged exactly: 1 × $0.02 apify-actor-start + 10 × $0.10 establishment-result = $1.02 on the Free tier — every delivered row was billed.

2. A smaller, narrowed run

{
"localAuthorityName": "Cambridge City",
"maxResults": 5
}

Caps the run at 5 rows — about $0.52 on the Free tier ($0.02 start + 5 × $0.10). localAuthorityName narrows what the source is asked for.

3. A full-size run

{
"localAuthorityName": "Cambridge City",
"maxResults": 100
}

Up to 100 rows (the schema default for maxResults) — about $10.02 on the Free tier ($0.02 start + 100 × $0.10) if the source has that many.

🧾 JSON sample record

One real record from run bztzbXdHh5fSaREgS, exactly as it lands in the dataset (emails masked, long text shortened):

{
"record_type": "establishment",
"fhrs_id": 1472773,
"business_name": "196",
"business_type": "Restaurant/Cafe/Canteen",
"business_type_id": 1,
"address": "196 Mill Road, Cambridge, Cambridgeshire, CB1 3NF",
"postcode": "CB1 3NF",
"local_authority": "Cambridge City",
"local_authority_id": "027",
"local_authority_email": "[email on record]",
"local_authority_website": "http://www.cambridge.gov.uk",
"rating_value": "5",
"rating_is_numeric": true,
"rating_date": "2026-02-25T00:00:00",
"scheme_type": "FHRS",
"new_rating_pending": false,
"hygiene_score": 5,
"structural_score": 5,
"management_score": 0,
"latitude": "52.1976012",
"longitude": "0.1440631",
"source_url": "https://ratings.food.gov.uk/business/en-GB/1472773",
"source_is": "the Food Standards Agency's own public ratings API",
"report_id": "7100bec1c91c7b44"
}

🔧 How it works

Source. The actor reads api.ratings.food.gov.uk — endpoints: https://api.ratings.food.gov.uk. Public pages and feeds only; nothing behind a login.

Transport. Plain HTTPS from the Apify platform, no proxy. robots.txt is read first and a disallowed path is never fetched. Pacing: RATE_PER_SEC=2.0, MIN_INTERVAL=1.0.

Terminal states. A run ends NORMAL, CAPPED (your cap was reached), PARTIAL (something was withheld and the message says what), GENUINE_EMPTY (the source was read and truly had nothing in scope) or BLOCKED (the source refused or changed shape — the run FAILS loud and bills nothing). A zero-row run is never reported as a silent success.

Charging. Each establishment record is charged at the moment it is pushed (establishment-result); a row that fails to charge is not delivered, so the dataset count always equals the charged count.

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:

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

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 wordsPass, 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

filterwhat it does
localAuthorityName / localAuthorityIdone of 363 authorities; ambiguous names refused
businessTypeName / businessTypeIdone of 14 published types
businessNamefree text, matched by the FSA against establishment names
ratingValueskeep only these ratings — applied here, not on the server
ratingAtOrBelownumeric ratings at or below N — the usual prospecting filter
maxResultshow many establishments to return — also your cost ceiling

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.

What is not done. No login, no cookie or CAPTCHA bypass, no private or personal-account data, no browser automation.

💰 Pricing example

EventFreeBronzeSilverGold
Actor Start (apify-actor-start)$0.02$0.02$0.02$0.02
Establishment record (establishment-result)$0.10$0.09$0.08$0.07

Worked at the live Free-tier price:

  • 8 establishment records: $0.02 start + 8 × $0.10 = $0.82
  • 25 establishment records: $0.02 start + 25 × $0.10 = $2.52
  • 100 establishment records: $0.02 start + 100 × $0.10 = $10.02

A run that delivers zero rows charges the $0.02 start fee only. A BLOCKED run (source refused) fails loud and charges no value event. The start fee is charged once per GB of run memory; the default run memory is 512 MB.

Yield on the sample run: NORMAL: returned=10 scanned=100 server_total=1486 | establishment-result charged=10 (one per returned establishment). maxResults is a hard ceiling on what is delivered and billed, never a target.

This actor reads public, logged-out pages and feeds published by api.ratings.food.gov.uk. It collects only what the source publishes to any visitor, keeps to the source's robots rules (checked on every run), identifies itself, and does not access accounts, private data or anything behind authentication. Use the output in line with the source's terms and your local law; the intended use is B2B research and monitoring.

❓ FAQ

Q: Do I need an API key or a login?
A: No. The source (api.ratings.food.gov.uk) is read logged-out; the input schema has no key field and the actor carries no secrets.

Q: Why did my run return 0 rows?
A: Read the run's status message. GENUINE_EMPTY means the source was read and had nothing in scope for your input; BLOCKED means the source refused and the run failed without billing a value event — retry later or narrow the input. A zero-row run bills the start fee only.

Q: How many rows can one run return?
A: Up to maxResults (default 100). Raise the cap for a bigger run; you pay per delivered row.

Q: How fresh is the data?
A: Every run reads the source live at run time; nothing is cached between runs. Put it on a schedule for a continuous feed.

Q: What formats can I export?
A: The dataset downloads as JSON, CSV, Excel, XML or RSS from the run's Dataset tab or the Apify API, and any run can push to a webhook or integration.

Q: How is this different from the other company registry checks actors?
A: Same output shape and billing model; this one covers api.ratings.food.gov.uk. The siblings under Related Actors cover the other sources or slices — run several on one schedule for a combined feed.

Q: Are there rate limits?
A: The actor paces itself against the source (RATE_PER_SEC=2.0, MIN_INTERVAL=1.0) and honours its robots rules; there is no per-buyer limit beyond your Apify plan's concurrency.

🆘 Troubleshooting

  • Run FAILED with BLOCKED → the source refused the request or changed its page shape → nothing was billed beyond the start fee; retry after a while, and if it persists open an Issue with the run id.
  • Status says CAPPED → your cap (maxResults) was reached → raise it for a bigger run.
  • Input validation error on start → a field is outside the schema's allowed values → start from the Quick start block and change one field at a time.
  • Run TIMED-OUT → a very wide request on a slow day → raise the run timeout in Run options or narrow the input; what was delivered before the timeout is still in the dataset.

⭐ Found this useful?

If this actor saved you a manual check, a quick review on the Apify Store helps other teams find it. Feature request or a source that changed? Open it from the Issues tab — every one is read.