# Product Recall Catalog Exposure Monitor (`bread-kim/product-recall-catalog-exposure-monitor`) Actor

Track product recall and catalog exposure changes. Get structured recall evidence for compliance, safety monitoring, and product risk workflows.

- **URL**: https://apify.com/bread-kim/product-recall-catalog-exposure-monitor.md
- **Developed by:** [bread kim](https://apify.com/bread-kim) (community)
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 results

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

## Product Recall Catalog Exposure Monitor

A private Apify Actor that retrieves **user-declared official recall feeds**, normalizes their fields, and compares affected brands, models, UPCs/GTINs, and manufacturing windows with a merchant catalog. Each dataset row explains exactly why a catalog item matched a recall.

This is a detection aid, not a legal determination. Review the linked official notice before delisting, notifying customers, or reporting to a regulator.

### What it does

- Accepts an inline catalog or a remote JSON/CSV catalog.
- Reads 1–20 JSON/CSV recall sources with explicit, safe dot-path mappings.
- Performs deterministic exact matching after Unicode/case/punctuation normalization.
- Rejects a known manufacturing date outside the recall window.
- Writes one evidence-backed exposure per catalog-item/recall pair to the default dataset.
- Writes counts and limit status to the `OUTPUT` key-value-store record.

No recall agency is hard-coded. This keeps network access under the operator's control and makes the Actor usable across jurisdictions. The operator must declare the final, official HTTPS feed URL and confirm its terms.

### Quick start

1. Copy `examples/input.json` into the Apify input editor.
2. Replace the `.example.gov` URL and mapping with a real official feed.
3. Run the Actor and review the **Exposure alerts** dataset view.
4. Treat `matchLimitReached: true` as incomplete output and rerun with a higher bounded limit or a narrower catalog.

For local development:

```bash
npm ci
npm run typecheck
npm test
npm run build
APIFY_LOCAL_STORAGE_DIR=./storage npm start
```

### Input contracts

Exactly one of `catalog` or `catalogUrl` is required. Remote catalogs must be a JSON array or CSV with the columns `id`, `name`, `brand`, `model`, `upc`, and `manufactureDate`. Only `id` is mandatory.

`demoMode: true` requires an inline catalog, performs no external HTTP requests, and uses one built-in deterministic recall fixture for automated QA. With `demoMode: false` (the default), `recallSources` remains required and production behavior is unchanged.

Each recall source supplies `mapping.id` and `mapping.title`, plus any matchable fields. Mappings are property-only dot paths, for example `results.items` or `Products.Model`; array traversal projects the named property from each array object. They are not JSONPath and cannot execute filters or code. List fields may be arrays or strings separated by `|`, `;`, comma, or newline.

JSON recall feeds may use `recordsPath` to select their record array. CSV feeds normally omit it. At least one identity path (`brand`, `models`, or `upcs`) is required. The run summary reports both total and matchable recalls and fails if a non-empty feed has no matchable records, which catches a common bad-mapping failure. See [the complete example](examples/input.json).

### Scoring

| Evidence | Score |
|---|---:|
| Exact UPC/GTIN (at least 6 digits) | 100 |
| Exact normalized model | 70 |
| Exact normalized brand | 25 |
| Manufacturing date inside a supplied window | +5 |

The default minimum is 70. By default a model only counts when brand also matches, preventing common model-number collisions. UPC stands alone. A known catalog manufacturing date outside the notice window rejects the match. Brand-only matching can be enabled deliberately by lowering `minScore`; it is likely to be noisy.

Normalization never uses fuzzy or semantic inference. UPCs retain leading zeroes after non-digits are removed. Always inspect `evidence` and the official notice.

### Bounded operation

Default / hard limits are 10,000 / 50,000 catalog rows, 5,000 / 25,000 recalls per source, 10,000 / 100,000 output matches, 5 MB / 25 MB per response, 30 / 60 seconds per request, and 4 / 10 concurrent downloads. Processing fails closed on a malformed or unavailable source; it never silently reports a partial clean bill of health.

All outbound destinations come from Actor input. They must use public HTTPS on port 443. DNS answers are checked and pinned for each connection, redirects are same-origin only, response compression is rejected, and streamed bodies are size-limited. See [SECURITY\_REVIEW.md](SECURITY_REVIEW.md).

### Output and repeat runs

Dataset rows are stable in shape but are not deduplicated across separate runs. Use `(catalogItemId, sourceName, recallId)` as the business key in downstream systems. `checkedAt` is the scan timestamp. A run with no matches produces an empty dataset and an `OUTPUT` summary with `exposureMatches: 0`.

For repeat monitoring, invoke this private Actor through your own approved process. This repository intentionally contains no schedule, webhook, credentials, proxy configuration, publishing, or deployment setup.

`canary_input.json` is the deterministic no-network QA input. It always matches the built-in demo recall and writes one exposure without depending on an external recall service.

### License and responsibility

The implementation is private/internal unless the owner chooses a license. Feed availability, accuracy, redistribution rights, and regulatory duties remain the operator's responsibility.

# Actor input Schema

## `demoMode` (type: `boolean`):

Use the built-in matching recall fixture without any external HTTP requests. Production runs leave this disabled.

## `catalog` (type: `array`):

Catalog rows. IDs must be unique. Use manufactureDate to eliminate out-of-range products.

## `catalogUrl` (type: `string`):

Public HTTPS URL of a JSON array or CSV catalog export.

## `catalogFormat` (type: `string`):

Select explicit parsing or infer CSV from content type/file extension; otherwise JSON.

## `recallSources` (type: `array`):

Required when demoMode is false. Only add official sources you are authorized to retrieve. Every URL is fetched with SSRF and size controls.

## `matching` (type: `object`):

Conservative deterministic scoring options.

## `limits` (type: `object`):

Per-run resource limits, bounded by non-overridable hard maximums.

## Actor input object example

```json
{
  "demoMode": true,
  "catalog": [
    {
      "id": "CANARY-SKU",
      "brand": "Example Brand",
      "model": "EX-100",
      "upc": "000123456789"
    }
  ],
  "catalogFormat": "auto",
  "matching": {
    "minScore": 70,
    "requireBrandWithModel": true
  },
  "limits": {
    "maxCatalogItems": 10,
    "maxRecallsPerSource": 25,
    "maxMatches": 25,
    "maxResponseBytes": 500000,
    "requestTimeoutSecs": 15,
    "concurrency": 1
  }
}
```

# Actor output Schema

## `exposures` (type: `string`):

Evidence-backed catalog-to-recall matches.

## `summary` (type: `string`):

Counts, truncation state, and per-source coverage.

# 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 = {
    "demoMode": true,
    "catalog": [
        {
            "id": "CANARY-SKU",
            "brand": "Example Brand",
            "model": "EX-100",
            "upc": "000123456789"
        }
    ],
    "catalogFormat": "auto",
    "matching": {
        "minScore": 70,
        "requireBrandWithModel": true
    },
    "limits": {
        "maxCatalogItems": 10,
        "maxRecallsPerSource": 25,
        "maxMatches": 25,
        "maxResponseBytes": 500000,
        "requestTimeoutSecs": 15,
        "concurrency": 1
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("bread-kim/product-recall-catalog-exposure-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 = {
    "demoMode": True,
    "catalog": [{
            "id": "CANARY-SKU",
            "brand": "Example Brand",
            "model": "EX-100",
            "upc": "000123456789",
        }],
    "catalogFormat": "auto",
    "matching": {
        "minScore": 70,
        "requireBrandWithModel": True,
    },
    "limits": {
        "maxCatalogItems": 10,
        "maxRecallsPerSource": 25,
        "maxMatches": 25,
        "maxResponseBytes": 500000,
        "requestTimeoutSecs": 15,
        "concurrency": 1,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("bread-kim/product-recall-catalog-exposure-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 '{
  "demoMode": true,
  "catalog": [
    {
      "id": "CANARY-SKU",
      "brand": "Example Brand",
      "model": "EX-100",
      "upc": "000123456789"
    }
  ],
  "catalogFormat": "auto",
  "matching": {
    "minScore": 70,
    "requireBrandWithModel": true
  },
  "limits": {
    "maxCatalogItems": 10,
    "maxRecallsPerSource": 25,
    "maxMatches": 25,
    "maxResponseBytes": 500000,
    "requestTimeoutSecs": 15,
    "concurrency": 1
  }
}' |
apify call bread-kim/product-recall-catalog-exposure-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bread-kim/product-recall-catalog-exposure-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/2doRXAiCegXdFZhYB/builds/RKlEBVrkEwbcRiv28/openapi.json
