# EU Pesticide MRL Watchlist Monitor (`chosen_loafer/eu-pesticide-mrl-monitor`) Actor

Checks a watchlist of commodity/pesticide-residue pairs against the EU Pesticides Database for MRLs. Reports limit, unit, definition, with change states (FIRST_SEEN, UNCHANGED, LIMIT_CHANGED, DEFINITION_CHANGED, NOT_FOUND). Informational only; Official Journal is authoritative. No personal data.

- **URL**: https://apify.com/chosen_loafer/eu-pesticide-mrl-monitor.md
- **Developed by:** [Pavel Staurouski](https://apify.com/chosen_loafer) (community)
- **Categories:** Automation, Business, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 watchlist item checkeds

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

## EU Pesticide MRL Watchlist Monitor

Checks a watchlist of commodity/pesticide-residue pairs against the European
Commission's EU Pesticides Database (food.ec.europa.eu/plants/pesticides/eu-pesticides-database_en)
for maximum residue levels (MRLs) under Regulation (EC) No 396/2005.

### What you get

One record per watchlist item per run: the current MRL and its unit (mg/kg), whether
it is marked "at or below the limit of quantification" (the published "\*" suffix), the
**verbatim** residue definition (never a bare CAS number or trade name - some residues
are defined as "sum of X, Y and Z, expressed as X", covering metabolites or a whole
substance group, and this Actor passes that full definition through exactly as
published), the regulation that set the current value and the date it became
applicable, and the EU's own previously-published value for that row (which may equal
the current one, if a later amendment only reconfirmed it without changing the figure).

Every record also carries an explicit state: `FIRST_SEEN`, `UNCHANGED`,
`LIMIT_CHANGED`, `DEFINITION_CHANGED`, `NOT_FOUND`, `INVALID_INPUT`, or `FETCH_ERROR`.
A failed fetch always produces an explicit `FETCH_ERROR` record per watchlist item,
never a silent "no change" - and never touches your previously-saved comparison state,
so the next successful run still compares against the last known-good snapshot.

### What it does not do

**No legal advice, no compliance certification.** The Commission states this database
"is made available solely for the purpose of information. It has no legal value."
The official information is published in the Official Journal of the European Union;
this Actor surfaces that disclaimer rather than overriding it. `NOT_FOUND` means the
database has no commodity-specific entry for that pair - the EU's generic default MRL
of 0.01 mg/kg (Regulation (EC) No 396/2005, Art. 18(1)(b)) still applies in that case,
this Actor just does not fabricate a row the Commission itself never published.
No personal data: every field traces to a commodity, a residue definition, a
regulation number or a date, never a contact name or signatory.

### Why you might need this

A food-testing lab, an importer's supplier-quality team, or a compliance consultant
tracking a fixed set of commodity/pesticide pairs needs to know immediately when an
MRL tightens, loosens, or the residue definition itself is redefined. Testing a batch
against a limit that was lowered last month, because nobody re-ran the Commission's
search tool against the growing watchlist, produces a result that reads "pass" against
stale data and "fail" (or a border rejection) against the regulation actually in force -
not a hypothetical, since amending regulations for a single residue are published
several times a year. This Actor turns that manual re-check into a scheduled run
instead of a recurring blind spot.

### Input

```json
{
  "watchlist": [
    { "commodityName": "Oranges", "residueName": "Glyphosate" },
    { "commodityId": 2, "residueId": 8 },
    { "commodityName": "Table grapes", "residueName": "Glyphosate" }
  ]
}
```

Each item needs a commodity, given as **either** `commodityId` (the database's own
numeric `product_id`, most reliable) **or** `commodityName` (exact, case-insensitive
match). Commodity names are **not unique** in the source: generic category names like
"Others (2)", "Muscle", "Kidney" or "Lentils" repeat across different hierarchy
branches (confirmed: "Others (2)" alone appears 47 times). An ambiguous name resolves
to `INVALID_INPUT` with every candidate's numeric id and full breadcrumb listed in
`errorMessage`, so you can pin the right one. It also needs a residue, given as
**either** `residueId` (numeric `pest_res_id`) **or** `residueName` (exact,
case-insensitive match; residue names are confirmed unique, so this is always safe).

Tip: run once with a best-guess name to discover the real id from the error detail,
then switch to `commodityId`/`residueId` for faster, unambiguous future runs.

### Sample output

```json
{
  "commodityId": 4,
  "commodityName": "Oranges",
  "commodityCode": 110020,
  "commodityPath": "FRUITS, FRESH or FROZEN; TREE NUTS > Citrus fruits > Oranges",
  "residueId": 120,
  "residueDefinition": "Glyphosate",
  "mrlUnit": "mg/kg",
  "currentMrlRaw": "0.5",
  "currentMrlValue": 0.5,
  "currentAtOrBelowLoq": false,
  "currentLegalReference": "Reg. (EU) No 293/2013",
  "currentApplicableDate": "2013-04-06",
  "currentFootnote": null,
  "previousPublishedMrlRaw": "0.5",
  "previousPublishedMrlValue": 0.5,
  "previousPublishedLegalReference": "Reg. (EU) No 441/2012",
  "previousPublishedApplicableDate": "2012-06-14",
  "sourceUrl": "https://ec.europa.eu/food/plant/pesticides/eu-pesticides-database/backend/api/pr/get_details_single_pr?pest_res_id=120&lang=en",
  "watchlistItem": { "commodityName": "Oranges", "residueName": "Glyphosate" },
  "matchState": "UNCHANGED",
  "changedFields": [],
  "checkedAt": "2026-10-08T12:02:06.828Z",
  "errorMessage": null
}
```

This is a real record from a live run: the current glyphosate MRL on oranges (0.5
mg/kg, set by Reg. (EU) No 293/2013), alongside the previously-published value the
Commission itself recorded before that amendment, so you can see at a glance that the
figure was reconfirmed rather than changed.

### Change tracking across runs

Run this Actor on a schedule. Each watchlist pair's last-seen snapshot (MRL, LOQ
marker, legal reference, applicable date, residue definition) is stored per Actor, so
every subsequent run reports exactly what changed since the last one, not just the
current state.

### Pricing

Pay per run started, plus per watchlist item successfully checked against the live
database (resolves to `FIRST_SEEN`, `UNCHANGED`, `LIMIT_CHANGED`, `DEFINITION_CHANGED`,
or `NOT_FOUND`). Items that fail to resolve (`INVALID_INPUT`) or hit a transient fetch
failure (`FETCH_ERROR`) are never charged. See the Pricing tab for exact rates.

# Actor input Schema

## `watchlist` (type: `array`):

Commodity/pesticide-residue pairs to check against the EU Pesticides Database's current maximum residue levels (MRLs). Each item needs a commodity, given as EITHER "commodityId" (the database's own numeric product_id, most reliable) OR "commodityName" (exact, case-insensitive match against the database's commodity list; many commodity names repeat across different hierarchy branches, e.g. "Others (2)", "Muscle", "Kidney" each appear 7+ times for different animal species, so an ambiguous name resolves to INVALID_INPUT with the candidate IDs listed - use commodityId for those). It also needs a residue, given as EITHER "residueId" (numeric pest_res_id) OR "residueName" (exact, case-insensitive match; residue names are unique in the database so this is safe). Run this Actor once with a broad guess to discover the right IDs from the INVALID_INPUT/NOT_FOUND error detail, then pin future runs to commodityId/residueId for speed and precision.

## Actor input object example

```json
{
  "watchlist": [
    {
      "commodityName": "Oranges",
      "residueName": "Glyphosate"
    },
    {
      "commodityId": 2,
      "residueId": 8
    },
    {
      "commodityName": "Table grapes",
      "residueName": "Glyphosate"
    }
  ]
}
```

# Actor output Schema

## `watchlistResults` (type: `string`):

Per-pair records.

# 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 = {
    "watchlist": [
        {
            "commodityName": "Oranges",
            "residueName": "Glyphosate"
        },
        {
            "commodityId": 2,
            "residueId": 8
        },
        {
            "commodityName": "Table grapes",
            "residueName": "Glyphosate"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("chosen_loafer/eu-pesticide-mrl-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 = { "watchlist": [
        {
            "commodityName": "Oranges",
            "residueName": "Glyphosate",
        },
        {
            "commodityId": 2,
            "residueId": 8,
        },
        {
            "commodityName": "Table grapes",
            "residueName": "Glyphosate",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("chosen_loafer/eu-pesticide-mrl-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 '{
  "watchlist": [
    {
      "commodityName": "Oranges",
      "residueName": "Glyphosate"
    },
    {
      "commodityId": 2,
      "residueId": 8
    },
    {
      "commodityName": "Table grapes",
      "residueName": "Glyphosate"
    }
  ]
}' |
apify call chosen_loafer/eu-pesticide-mrl-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,chosen_loafer/eu-pesticide-mrl-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/2ewy0d8GPsMunmazy/builds/TKECAaLbXJkQb9s6w/openapi.json
