# EU & France Product Recalls – RappelConso + Safety Gate (`shadow-tools/eu-france-product-recalls`) Actor

Search, look up and watch product recalls from RappelConso (France) and the EU Safety Gate (RAPEX): brand, risk, measures, lots, barcodes, images, official notice. Unofficial tool using official open data.

- **URL**: https://apify.com/shadow-tools/eu-france-product-recalls.md
- **Developed by:** [Shadow Tools](https://apify.com/shadow-tools) (community)
- **Categories:** Business, Automation, News
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 recall alerts

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 & France Product Recalls – RappelConso + Safety Gate

Call this Actor to find out **whether a product is recalled or flagged dangerous** in France (RappelConso) or the EU (Safety Gate / RAPEX): by keyword, brand, barcode (GTIN/EAN), risk or notice number. Use `search` for a question about a period, `lookup` when you already have a recall id (`2026-11-0037`), a Safety Gate case (`SR/03023/26`) or an official URL, and `watch` only for scheduled runs (it returns only recalls new since the last run). Each record carries the official notice URL.

#### Minimal input per mode

| Mode | Minimal input | What comes back |
|---|---|---|
| `search` | `{"keywords": ["lait"], "daysBack": 30, "maxResults": 5}` | recalls matching the filters in the period |
| `watch` | `{"mode": "watch", "keywords": ["jouet", "toy"], "daysBack": 3, "maxResults": 5}` | only recalls not delivered by a previous run with the same filters |
| `lookup` | `{"mode": "lookup", "lookupIds": ["2026-11-0037", "SR/03023/26"]}` | one record per reference found |

Short output (real record, 4 Nov 2026): `{"source": "RAPPELCONSO", "id": "2026-11-0029", "publicationDate": "2026-11-04T11:49:07+00:00", "title": "lessive liquide universelle sidolux muguet 1 l", "brand": "sidolux", "category": "maison-habitat", "risks": ["risque chimique"], "barcodes": ["5902986201172"], "url": "https://rappel.conso.gouv.fr/fiche-rappel/24009/interne"}` (full records below).

#### For AI agents (MCP)

- **One call = one answer.** Default `maxResults` is 20 (per source). Keep it small and raise it only when the run SUMMARY says a source is `capped: true`. A typical run takes 5-60 s.
- **Errors fail fast** in the form `Field X invalid: expected Y, got Z, example W` (for example `Field dateFrom invalid: expected a real date YYYY-MM-DD, got "05/11/2026", example 2026-10-01.`). Fix that field and call again; nothing is charged.
- **0 results is not an error.** The run succeeds with an empty dataset and the key-value store record `SUMMARY` carries `note` with the reason and how to widen (more `daysBack`, more general keywords, French words for RappelConso and English for Safety Gate, fewer filters). In `lookup` mode the note lists the references not found. The same `note` is written when a result is capped or partial.
- **`watch` semantics**: state is kept per Actor and per user store, for each set of sources and filters. The first watch run returns the recent alerts of the period (`daysBack`); later runs return only what is new, so 0 results usually means nothing new was published.
- Dates are `YYYY-MM-DD`. Search by product words (`lait`, `toy`), brand, or barcode (digits only); use `lookup` for a known reference instead of searching.

> **Unofficial tool. Not affiliated with, endorsed by or sponsored by the DGCCRF, the French government, RappelConso, the European Commission or the Safety Gate team.** It reads their official open data and reformats it.

Search, look up and **watch** product recalls and dangerous-product alerts from two official sources, in **one clean schema**:

| Source | Coverage | Origin |
|---|---|---|
| **RappelConso** (France) | All consumer product recalls published in France: food, cars, baby items, home, electrical, cosmetics, clothing, sports… | DGCCRF open dataset (Opendatasoft API on data.economie.gouv.fr) |
| **Safety Gate / RAPEX** (EU/EEA) | Alerts on dangerous **non-food** products notified by EU/EEA countries (measures, risk level, origin, photos) | European Commission public XML export |

### Who uses it

- **E-commerce sellers and marketplaces**: screen your catalogue (by barcode, brand or keyword) against fresh recalls and take products down before a regulator does.
- **Importers and distributors**: follow alerts on your product categories and your suppliers' brands.
- **Compliance and quality teams**: daily digest of new recalls in your sector (watch mode on a schedule), with the official notice link for your records.
- **Retail and food-service chains**: check a list of barcodes or brands each morning against RappelConso, plus the EU Safety Gate for non-food goods.
- **Insurers and brokers**: track recalls and hazards by category and brand.
- **Journalists, consumer associations and researchers**: dated, structured recall history (about one year per run, more with several runs) to count and compare hazards, brands or countries.
- **Developers**: feed recalls into a ticketing tool, a chat channel or a database through a webhook or the API.

### Modes

- **search**: recalls matching your filters over a period (default: the last 7 days).
- **watch**: only recalls **not delivered by a previous run with the same filters**. Put it on a schedule (hourly or daily) and connect a webhook or an integration. A new *version* of a RappelConso sheet (an update to a recall) counts as new.
- **lookup**: fetch specific notices from a RappelConso sheet number (`2026-11-0037`) or URL, or a Safety Gate case number (`SR/03023/26`) or alert URL.

Filters (all optional, combined with AND; several values inside one filter are OR): keywords, brands, categories, risks/hazards, distributors (RappelConso only), barcodes (GTIN/EAN), period.

### Quick start

Watch new recalls about toys and chargers every day (schedule it, set a webhook):

```json
{ "mode": "watch", "keywords": ["jouet", "toy", "chargeur", "charger"], "daysBack": 3 }
```

Check a list of barcodes against the last year:

```json
{ "mode": "search", "daysBack": 365, "barcodes": ["3467980036813", "5902986201172"] }
```

Listeria and salmonella recalls in France this month, sold at Leclerc:

```json
{ "sources": ["RAPPELCONSO"], "daysBack": 30, "hazards": ["listeria", "salmonelle"], "distributors": ["leclerc"] }
```

Look up two notices:

```json
{ "mode": "lookup", "lookupIds": ["2026-11-0037", "SR/03153/26"] }
```

Call it from the API: `POST https://api.apify.com/v2/acts/<ACTOR>/run-sync-get-dataset-items?token=<TOKEN>` with the JSON input as body.

### What you get

One record per alert, same fields for both sources (empty when the source has no value):

`source`, `id`, `version`, `publicationDate`, `title`, `productName`, `brand`, `category`, `subCategory`, `riskLevel` (Safety Gate), `risks[]`, `recallReason` (RappelConso), `hazardDescription`, `recallType`, `measures[]`, `compensation[]`, `notifyingCountry`, `salesArea[]`, `originCountry`, `distributors[]` (RappelConso), `barcodes[]`, `lots[]`, `models[]`, `products[]` (barcode + lot + date pairs, RappelConso), `images[]`, `url` (**official notice**), `noticePdfUrl`, `recallUrl`, `description`, `matchedKeywords[]`, `attribution`, `extractedAt`, `language`.

RappelConso texts are in French; Safety Gate texts follow the `language` option (English by default; fr, de, es, it, nl, pt, pl available).

Example (RappelConso, real record from a run on 5 Nov 2026, shortened):

```json
{
  "source": "RAPPELCONSO",
  "id": "2026-11-0029",
  "publicationDate": "2026-11-04T11:49:07+00:00",
  "title": "lessive liquide universelle sidolux muguet 1 l",
  "brand": "sidolux",
  "category": "maison-habitat",
  "subCategory": "produits chimiques",
  "risks": ["risque chimique"],
  "measures": ["ne plus utiliser le produit", "rapporter le produit au point de vente"],
  "compensation": ["remboursement"],
  "notifyingCountry": "France",
  "salesArea": ["france entière"],
  "barcodes": ["5902986201172"],
  "lots": ["tous les lots"],
  "images": ["https://rappel.conso.gouv.fr/image/a8ccd023-8023-4180-8478-6776960e4cc2.jpg"],
  "url": "https://rappel.conso.gouv.fr/fiche-rappel/24009/interne"
}
```

Example (Safety Gate, real alert, shortened):

```json
{
  "source": "SAFETY_GATE",
  "id": "SR/03148/26",
  "publicationDate": "2026-11-05",
  "title": "Atom kävelysauvat / Trekking poles",
  "brand": "Atom",
  "category": "Hobby/sports equipment",
  "riskLevel": "Serious risk",
  "risks": ["Chemical"],
  "hazardDescription": "The product has an excessive concentration of lead (measured value: 0,33% by weight). ...",
  "measures": ["Import rejected at border"],
  "notifyingCountry": "Finland",
  "originCountry": "People's Republic of China",
  "barcodes": ["6410412307088"],
  "images": ["https://ec.europa.eu/safety-gate-alerts/public/api/notification/image/11239833"],
  "url": "https://ec.europa.eu/safety-gate-alerts/screen/webReport/alertDetail/10119539"
}
```

### Pricing

Pay per event, no subscription:

| Event | Price | Per 1,000 |
|---|---|---|
| `recall-alert`: one alert delivered (one dataset record) | **0.004 USD** | **4 USD per 1,000 alerts** |
| Actor start (once per run) | 0.00005 USD | n/a |

The start event is tiny; a run that returns nothing costs only that. Example: 200 alerts cost 0.80 USD plus the start event.

You can cap spend per run with the platform's **Maximum cost per run** setting: the Actor reads it, stops cleanly when the cap is reached and returns the partial results it already delivered (the run needs a cap high enough for at least one alert, otherwise it stops with a clear error and nothing is charged). In **watch** mode, alerts not delivered because of the cap are not marked as seen: they stay "new" for the next run.

### Related Actors

This Actor stands alone. By the same publisher, for other French and UK public data: [ADEME Emission Factors](https://apify.com/shadow-tools/ademe-emission-factors) and [French Public Contract Awards (DECP)](https://apify.com/shadow-tools/french-public-procurement-awards-decp). They cover unrelated data; use them only if you need them.

### Limits and good to know

- **Unofficial; data may be incomplete or late.** Always check the official notice (`url`) before acting. Recall data can be corrected by the authorities after delivery.
- **Freshness**: RappelConso showed a sheet published about 2 minutes earlier when checked (one observation). The Safety Gate export is refreshed hourly (08:00–19:00 CET) and only contains alerts the Commission has already published.
- **Safety Gate** has no server-side filter: the Actor downloads the period (28 days per request, up to about 13 months per run) and filters it itself. It has no distributor field, so a distributor filter skips it (the log says so).
- Keywords match at the start of a word (accents and case ignored); `matchedKeywords` shows which ones matched. RappelConso is searched with French words, Safety Gate with the chosen language: add both (`jouet`, `toy`).
- `maxResults` (default 20, max 5000) is per source. A full year is about 3,500 RappelConso and 3,900 Safety Gate alerts, retrieved in about a minute.
- RappelConso search windows are limited to 10,000 results by the source API; split very long periods.

### Privacy / GDPR by design

This Actor is deliberately built **not** to return personal data about natural persons (names, addresses, phone numbers, e-mails of individuals or sole traders). This is a design choice made to respect the GDPR.

- The contact phone number field of RappelConso sheets is never read.
- E-mail addresses and phone numbers found in free-text fields are removed. Distributor paragraphs containing an address, e-mail, phone number or legal identifier (SIRET, VAT, RCS...) are dropped entirely.
- Not guaranteed: small businesses can be named after their owner in the official data (for example a farm or a shop), so such a name can still appear in `brand` or `distributors`; other free text may mention a name. Product photos come from the official notices. You remain responsible for your own use of the data.
- The Actor stores nothing about you beyond your run's own dataset and logs on Apify.

### Licences and attribution

- RappelConso: Licence Ouverte v2.0 (Etalab), DGCCRF / Ministère de l'Économie. Source and extraction date are in each record's `attribution`.
- Safety Gate: © European Union, reused under Commission Decision 2011/833/EU (CC BY 4.0). Product photos and trademarks may belong to third parties.

### En français (court)

Outil **non officiel** qui reprend les données ouvertes de RappelConso (DGCCRF) et du Safety Gate (Commission européenne) : recherche, consultation d'une fiche, et mode « surveillance » des nouveaux rappels (à planifier). Aucune donnée de contact personnelle. Vérifiez toujours la fiche officielle.

# Actor input Schema

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

Pick one. "search" = use for a question about recalls in a period (is product X recalled, which recalls this week): returns recalls matching your filters. "watch" = use only for scheduled (hourly or daily) runs: returns only recalls not delivered by a previous run with the same filters, state is kept per Actor and user store, and the first watch run returns the recent alerts of the period. "lookup" = use when you already have a reference: a RappelConso recall id like 2026-11-0037, a Safety Gate case like SR/03023/26 or an official notice URL (put it in lookupIds). Allowed values: search, watch, lookup. Default: search.

## `sources` (type: `array`):

Sources to read, as a list, e.g. \["RAPPELCONSO"]. Allowed values: RAPPELCONSO (France, all consumer products incl. food, cars, children's items; French text) and SAFETY_GATE (EU/EEA alerts on dangerous non-food products; text in the chosen language). Default: both.

## `keywords` (type: `array`):

Words searched in the product name, brand, category and risk, as a list, e.g. \["lait", "poussette"]. ANY of them matches (OR), at the start of a word, accents and case ignored. RappelConso is in French, Safety Gate in the chosen language: add both ("jouet", "toy"). Empty = no keyword filter.

## `brands` (type: `array`):

Brand names as a list, e.g. \["Lidl", "Philips"]. A recall matches if its brand contains ANY of them. Empty = all brands.

## `categories` (type: `array`):

Category words as a list, e.g. \["alimentation"] for RappelConso (French: alimentation, maison-habitat, hygiène-beauté, sports-loisirs, automobiles...) or \["Toys"] for Safety Gate (English: Toys, Cosmetics, Electrical appliances and equipment...). Any of them matches (OR). Empty = all categories.

## `hazards` (type: `array`):

Risk words as a list, e.g. \["listeria", "salmonelle", "fire", "choking"]. Matched against the risk type and the recall reason; any of them matches (OR). Empty = all risks.

## `distributors` (type: `array`):

Retailers or marketplaces as a list, e.g. \["leclerc", "amazon"] (RappelConso only). Safety Gate has no distributor data: if you set this, Safety Gate is skipped and the run log says so. Empty = all distributors.

## `barcodes` (type: `array`):

Barcodes (GTIN/EAN, 8 to 14 digits) as a list, e.g. \["3467980036813"]. Only recalls listing a matching barcode are returned; spaces and dashes are ignored. To check a catalogue, also set daysBack to 365. Empty = no barcode filter.

## `lookupIds` (type: `array`):

Used only in lookup mode: list of references, e.g. \["2026-11-0037", "SR/03023/26"]. Accepts a RappelConso recall id (YYYY-MM-NNNN), a Safety Gate case number (SR/NNNNN/YY) or the official notice URL. Safety Gate lookup scans the last year of alerts (or the year in the case number).

## `daysBack` (type: `integer`):

Period length in days ending today, integer 1-365, e.g. 7 (default 7). Ignored if dateFrom is set. A small value is faster; Safety Gate is read in blocks of 28 days.

## `dateFrom` (type: `string`):

Start of the period, format YYYY-MM-DD, e.g. 2026-10-01. Overrides daysBack. Leave empty to use daysBack.

## `dateTo` (type: `string`):

End of the period, format YYYY-MM-DD, e.g. 2026-10-31. Default: today. Must be on or after dateFrom.

## `onlyNew` (type: `boolean`):

Search mode only, true or false (default false): skip recalls already delivered by a previous run with the same sources and filters. Always on in watch mode. A new version of a RappelConso sheet counts as new.

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

Maximum number of recalls returned per source, integer 1-5000, default 20, e.g. 20. Keep it small; raise it only if the run SUMMARY says a source is capped. Your run cost limit also applies to the total.

## `language` (type: `string`):

Language of Safety Gate texts. Allowed values: en, fr, de, es, it, nl, pt, pl (default en). RappelConso is always French.

## Actor input object example

```json
{
  "mode": "search",
  "sources": [
    "RAPPELCONSO"
  ],
  "keywords": [
    "lait"
  ],
  "brands": [
    "Lidl"
  ],
  "categories": [
    "alimentation"
  ],
  "hazards": [
    "listeria"
  ],
  "distributors": [
    "leclerc"
  ],
  "barcodes": [
    "3467980036813"
  ],
  "lookupIds": [
    "2026-11-0037"
  ],
  "daysBack": 7,
  "dateFrom": "2026-10-01",
  "dateTo": "2026-10-31",
  "onlyNew": false,
  "maxResults": 20,
  "language": "en"
}
```

# Actor output Schema

## `recalls` (type: `string`):

Matching recalls with brand, risk, measures, lots, barcodes, images and link to the official notice.

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

Every field of every alert, downloadable as JSON, CSV or Excel.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("shadow-tools/eu-france-product-recalls").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("shadow-tools/eu-france-product-recalls").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 '{}' |
apify call shadow-tools/eu-france-product-recalls --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,shadow-tools/eu-france-product-recalls"
        }
    }
}
```

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/B4SJ19jStpuecTXUl/builds/A0c6JFjy2v2LrJpe7/openapi.json
