# UK Food Alerts API: Allergy & Product Recall Watch (FSA) (`mouadapi/uk-food-alerts`) Actor

UK food allergy alerts and product recalls from the Food Standards Agency's public API, or only new and updated alerts since your last run. Never charged for failed or unchanged rows. Not affiliated with or endorsed by the Food Standards Agency.

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

## Pricing

from $1.00 / 1,000 alert returneds

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/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

Returns UK food allergy alerts and product recalls from the Food Standards Agency's public food alerts API as flat rows,
or in watch mode only the alerts that are new or updated since your last run. Never charged for failed or unchanged rows.

Choose alert types (allergy alerts, product recall information notices, food alerts for action) and a window; get one row
per alert with the business, products and pack sizes, batch codes and best-before dates, allergens, the risk, what the
business is doing, what consumers should do and a link to the alert page. Built for food businesses, retailers, compliance
teams, dashboards and AI agents that need UK food alerts without polling the FSA site. *Not affiliated with or endorsed by
the Food Standards Agency.*

### What it does

- **Export mode:** every alert published or updated in the window (default: the last 30 days), one flat row per alert.
- **Watch mode** (give a watch list name in `stateName`): remembers every alert and returns only:
  - `baseline`: the first run of the list;
  - `new`: an alert the list did not have (a new alert, or an FSA update with its own `-update-N` number);
  - `changed`: the FSA changed an alert (`changedFields` and `previousValues` say what and from what);
  - unchanged alerts are free and left out (`includeUnchanged: true` returns them as free rows).
  - A watch run with nothing new or updated returns **exactly one free `no_data` row** that says so.
- **No personal data, every alert kept:** a business name that is, or may be, a person's name (for example a sole trader
  trading under their own name) is left out of the alert: `business` loses that name (only that one when several
  businesses are named) and `businessOmitted` is `true`; the name is removed from every other text field, a field that
  still holds a word of it is `null`, and a title that named the person becomes a neutral title such as
  "Allergy alert: Steak Pie (400g)". When in doubt, the name is removed. The alert itself (products, batches, allergens,
  risk and advice) is returned and charged like any other.
- **You are never charged for failed results:** failed, no\_data and unchanged rows are free.

### Quick start

The last 30 days of allergy alerts and product recalls:

```json
{ "alertTypes": ["AA", "PRIN"] }
```

Watch for new and updated alerts (the first run is the baseline; later runs return only new and updated alerts; run it
daily with an Apify schedule):

```json
{ "stateName": "uk-food-alerts" }
```

Only alerts that name milk or peanut, over the last year:

```json
{ "alertTypes": ["AA"], "allergens": ["milk", "peanut"], "sinceDays": 365 }
```

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `alertTypes` | list of `AA`, `PRIN`, `FAFA` | all three (prefilled `AA`, `PRIN`) | Allergy alerts, product recall information notices, food alerts for action |
| `sinceDays` | integer 1–3,650 | `30` | Alerts published or updated in the last N days |
| `search` | string | — | Optional word or phrase (the FSA API search), e.g. `chocolate`. Never a URL |
| `allergens` | list of strings | — | Optional: only alerts that name one of these allergens (`milk`, `peanut`, `sesame`, …) |
| `mode` | `watch` or `export` | — | Empty: watch when `stateName` is given, otherwise export. An explicit mode wins |
| `stateName` | string | — (prefilled `uk-food-alerts`) | Watch list name. Watch mode without a name uses the list `default` |
| `includeUnchanged` | boolean | `false` | Watch: also return unchanged alerts (free) |
| `maxItems` | integer 1–100,000 | `1000` | Most charged alerts per run; in watch mode the rest come in the next run |

Field name from other tools: `types` (→ `alertTypes`). Changing the types, `search` or `allergens` of a watch list starts
a new baseline; changing `sinceDays` does not.

### Output

```json
{
    "status": "ok",
    "attempts": 1,
    "error": null,
    "input": "PRIN",
    "mode": "export",
    "watchList": null,
    "dropReason": null,
    "alertId": "FSA-PRIN-45-2026",
    "alertType": "PRIN",
    "alertTypeName": "Product Recall Information Notice",
    "title": "Waitrose recalls Waitrose Essential Unsweetened Oat Drink because of poor temperature control",
    "shortTitle": null,
    "created": "2026-09-22",
    "modified": "2026-09-22T19:55:28.156Z",
    "alertStatus": "Published",
    "business": "Waitrose",
    "businessOmitted": false,
    "products": "Waitrose Essential Unsweetened Oat Drink (1 litre)",
    "batches": "Waitrose Essential Unsweetened Oat Drink: …",
    "allergens": null,
    "riskStatement": "Waitrose recalls Waitrose Essential Unsweetened Oat Drink due to the risk of microbiological contamination and spoilage.",
    "actionTaken": "Waitrose is recalling the above product. …",
    "consumerAdvice": "If you have bought any of the above product, do not eat it. …",
    "countries": null,
    "previousAlertId": null,
    "changeType": null,
    "changedFields": null,
    "previousValues": null,
    "url": "https://alerts.food.gov.uk/news-alerts/alert/fsa-prin-45-2026",
    "source": "Food Standards Agency food alerts API. Contains public sector information licensed under the Open Government Licence v3.0 (Food Standards Agency).",
    "license": "OGL v3",
    "licenseUrl": "https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/",
    "scrapedAt": "2026-09-30T12:00:00.000Z"
}
```

| Status | Meaning | Charged? |
|---|---|---|
| `ok` | An alert returned | Export: yes. Watch: `baseline`, `new` and `changed` yes; `unchanged` no |
| `no_data` | No alert of that type in the window, or nothing new since the last run | No |
| `failed` | Invalid alert type, or no answer after retries (`error` says why) | No |

The key-value store holds `RUN_REPORT` (counts, charged and free rows, stop reason) and, when something fails, the raw
response (`SNAPSHOT_*`).

### Use it from AI agents

One clear call: `{}` returns the last 30 days of every alert type; `{"stateName": "<list>"}` returns only new and updated
alerts on later calls. Every row has `status`, `error`, `url` and `scrapedAt`. Call it through the Apify API, the Apify MCP
server (`mouadapi/uk-food-alerts`) or x402 agentic payments. Copy-paste call (your Apify token in `APIFY_TOKEN`):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/mouadapi~uk-food-alerts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"alertTypes": ["AA", "PRIN"]}'
```

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('mouadapi/uk-food-alerts').call({ alertTypes: ['AA', 'PRIN'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Pricing

Pay per event: one `alert` event per returned alert (export: every alert; watch: `baseline`, `new` and `changed` rows).
`no_data`, `failed` and unchanged rows are free.

| Plan | Price per 1,000 alerts |
|---|---|
| Free | $1.50 |
| Bronze | $1.30 |
| Silver | $1.15 |
| Gold (and higher) | $1.00 |

Apify also charges its small per-run start event. There are no usage fees on top.

### Limits

- One request at a time, at most one a second: the FSA publishes no rate limit, so this Actor keeps well under any
  reasonable use. If the API answers HTTP 429, the run pauses 60, 120 and 240 seconds on the same connection, then stops
  with free `failed` rows. No other IP or proxy is ever tried.
- At most 1,000 alerts per type and run (the FSA publishes a few dozen alerts a month).
- The FSA's `search` matches its own text index; `allergens` uses the FSA's allergen codes (plain words such as `milk`).

### Known issues

- How names are handled: a business trading under a person's name, and a brand name shaped like a person's name, are left
  out of the alert's fields (`businessOmitted: true`; the test leans towards removing). Such an alert has a neutral title
  and may have `null` in a text field that repeated the name. `RUN_REPORT` counts these alerts (`businessNamesOmitted`)
  and the fields set to null (`textFieldsNulled`), never the names.

### FAQ

**Does it include every UK food alert?** Every alert the FSA publishes in its food alerts API for the types you choose.
A business name that may be a person's is left out of the alert's fields (above); the alert is still returned.

**Why did a watch run return one `no_data` row?** Nothing was new or updated since the last run of the watch list; the
row says how many alerts were unchanged. It is free.

### Data and licence

- Source: the Food Standards Agency food alerts API (data.food.gov.uk), documented at
  https://data.food.gov.uk/food-alerts/ui/reference.
- Licence: Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
  Every row carries the attribution "Contains public sector information licensed under the Open Government Licence v3.0".
- This Actor is not affiliated with, endorsed by or provided by the Food Standards Agency. It returns the alerts as
  published; always check the linked alert page before acting on it.

# Actor input Schema

## `alertTypes` (type: `array`):

AA (allergy alerts), PRIN (product recall information notices), FAFA (food alerts for action). Empty: all three.

## `sinceDays` (type: `integer`):

Alerts published or updated in the last N days. Watch mode reads the same window and returns only new and updated alerts.

## `search` (type: `string`):

Optional word or phrase (the FSA API search), e.g. chocolate. Never a URL.

## `allergens` (type: `array`):

Optional: only alerts that name one of these allergens (e.g. milk, peanut, sesame).

## `mode` (type: `string`):

Empty: watch when a watch list name is given, otherwise export. "watch" returns only alerts that are new or changed since the last run of the watch list; "export" returns every record. An explicit mode wins.

## `stateName` (type: `string`):

Name of your watch list (kept in your own storage between runs). Giving a name turns on watch mode. Watch mode without a name uses the list "default".

## `includeUnchanged` (type: `boolean`):

Watch mode: also return alerts that did not change, as free rows.

## `maxItems` (type: `integer`):

Most alerts returned and charged per run. In watch mode the rest come in the next run.

## Actor input object example

```json
{
  "alertTypes": [
    "AA",
    "PRIN"
  ],
  "sinceDays": 30,
  "stateName": "uk-food-alerts",
  "includeUnchanged": false,
  "maxItems": 1000
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one row per record, or per entry when nothing is returned

## `runReport` (type: `string`):

Summary of the run (counts, charged and free rows, stop reason)

# 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 = {
    "alertTypes": [
        "AA",
        "PRIN"
    ],
    "stateName": "uk-food-alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mouadapi/uk-food-alerts").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 = {
    "alertTypes": [
        "AA",
        "PRIN",
    ],
    "stateName": "uk-food-alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("mouadapi/uk-food-alerts").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 '{
  "alertTypes": [
    "AA",
    "PRIN"
  ],
  "stateName": "uk-food-alerts"
}' |
apify call mouadapi/uk-food-alerts --silent --output-dataset

```

## MCP server setup

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

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/qc5DetW4DlxY3S3mp/builds/vI6ViTZjfe32Hqbsk/openapi.json
