# Food Recall Inventory Matcher (`afkdigital/food-recall-inventory-matcher`) Actor

Compare your own food inventory CSV or JSON with official FDA recall enforcement records; lot-level evidence, manual review flags and source links.

- **URL**: https://apify.com/afkdigital/food-recall-inventory-matcher.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 16.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 verified fda lot candidates

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

## Food Recall Inventory Matcher

Compare an authorized retailer/distributor inventory CSV or JSON file against official openFDA food enforcement recall reports. Returns explainable, conservative candidate matches with FDA source links. No FDA API key.

**Not legal, food-safety, medical or compliance advice.** FDA recalls may be revised or delayed. Never treat this output as a clearance to sell or as a final determination that a particular lot is affected. This Actor is not sponsored or endorsed by the FDA.

### Quick start

Supply exactly one of inventoryFiles (Apify upload UI, with a readable HTTPS URL), inventoryFileUrl (authorized HTTPS link), inventoryRows (inline JSON array), or inventoryStoreId (an authorized private Apify key-value store selected with READ permission). For private uploads, first upload the CSV/JSON file to your own Apify key-value store, then choose that store under **Authorized inventory storage** and enter its record key in **Filename in selected storage** (for example, INVENTORY.csv). Do not put Apify tokens in links. File-upload URLs from private stores cannot be read unless the Actor has explicit READ permission via the storage picker. UTF-8 CSV template:

```csv
row_id,product_name,brand,manufacturer,lot,barcode,sku
INV-001,Todd's Seeds Salad Sprouting Mixture,Todd's Seeds,TODD'S SEEDS LLC,TS-633-2,,SEEDS-01
INV-002,Unrelated Invented Cereal,Example Brand,Example Foods,LOT-XYZ,,
```

JSON Actor input example:

```json
{
  "inventoryRows": [
    {"row_id":"INV-001","product_name":"Todd's Seeds Salad Sprouting Mixture","brand":"Todd's Seeds","manufacturer":"TODD'S SEEDS LLC","lot":"TS-633-2"}
  ],
  "matchThreshold": 0.75,
  "maxResults": 50,
  "maxMatchesPerRow": 3,
  "maxDataAgeDays": 21
}
```

Required customer fields: row_id (opaque inventory row identifier) and product_name. Optional: brand, manufacturer, lot, barcode, sku. File input supports CSV headers or an array of JSON objects (also an object with rows array). Maximum 1,000 rows and 3 MB of file content. All unknown columns are rejected. Never upload personal names, email addresses, addresses, telephone numbers, or any other personal data. Some personal contact patterns are rejected automatically, but customers remain responsible for excluding all personal data. Only rowId is copied from the customer's file into the output dataset; user input is stored by Apify according to the platform's storage/retention policies.

### Matching and uncertain evidence

The Actor downloads every currently listed openFDA food/enforcement ZIP partition from the official FDA downloads manifest, for a complete snapshot on each run.

- **strong_match**: a very strong product-description match AND an exact inventory lot appearing in FDA code_info (or an explicit FDA all-lots statement). No conflicting manufacturer/recalling-firm or brand evidence. This is still a *candidate* match.
- **review_required**: the product appears related but the lot, brand, recalling firm, or evidence coverage cannot confirm scope. Lot and firm disagreements explicitly force manual review.
- **no_match**: no credible candidate was found in this FDA snapshot. This is NOT a finding that the product is safe or unaffected.

FDA recalling_firm is not necessarily the legal manufacturer of the product. FDA food enforcement records do not guarantee a separate structured UPC, SKU, or brand field; the Actor only marks these fields as matched if the published text supports them. Source product description and code information are passed through without inventing lot ranges. FDA report_date, recall_initiation_date, classification, recalling firm, and status are included only if present.

Every candidate provides the FDA recall number and an official openFDA record URL derived from that identifier. Unverified/missing lot or manufacturer evidence cannot produce a strong_match.

### Output schema

The Apify default dataset contains records with rowId, matchStatus, confidenceLevel, confidenceScore, fdaRecallNumber, fdaEventId, productDescription, fdaCodeInfo, lotScope, fdaRecallingFirm, recallDate (FDA report_date), recallInitiationDate, classification, fdaStatus, sourceUrl, matchedFields, conflictingFields, reviewReasons, recordFingerprint, recordChangeStatus, fdaExportDate, chargeableEvent and disclaimer. Absent FDA fields remain absent/null; a no_match row has no recall ID or evidence URL.

The SUMMARY record in the default key-value store contains source export date, FDA record/partition counts, inventory row count, emitted/strong/review/no-match counts, charged event count, truncation flag, and payment-limit flag. The output limit may stop before all inventory rows are inspected; unprocessed rows are never silently marked no_match.

### Track corrected or updated FDA records

FDA ZIP downloads are snapshots and old records can change. Every run downloads all partitions, rather than trying incremental updates. Each candidate includes a SHA-256 recordFingerprint of the original FDA JSON record. For comparisons between runs, pass previousResults with an array of objects containing ONLY recallNumber and recordFingerprint copied from prior outputs. The updated output reports recordChangeStatus: updated, unchanged, or not_compared. This does not prove a recall changed legal status or detect removed records that are no longer matched.

### Transparent event billing

**Active price: $0.01 per official-source-backed strong_match** through the custom Apify Pay-per-event (PPE) event `verified-match`. The configured chargeable events list contains **only `verified-match`**, with neither automatic `apify-actor-start` nor `apify-default-dataset-item` charges. Ambiguous `review_required` results, `no_match` results, malformed input and source/network failures trigger **no Actor PPE event charge**. The Actor's [current Apify Store pricing page](https://apify.com/afkdigital/food-recall-inventory-matcher) is authoritative for the event price and any separate platform charges. Post-run data storage/access or Apify subscription fees may be subject to Apify's platform terms. Users can set a maximum charge per run.

### Official source and licensing

- FDA ZIP exports and download strategy: https://open.fda.gov/apis/downloads/
- Official current partition manifest: https://api.fda.gov/download.json
- Food recall enforcement: https://open.fda.gov/apis/food/enforcement/
- openFDA licensing: https://open.fda.gov/license/
- openFDA terms: https://open.fda.gov/terms/
- Apify PPE monetization: https://docs.apify.com/actors/publishing/monetize/pay-per-event
- Apify Store publishing requirements: https://docs.apify.com/actors/publishing/publish

FDA materials on openFDA are generally CC0 unless otherwise stated. CC0 does NOT waive trademarks, patents, publicity, privacy, third-party copyright or specially restricted data. For example, GMDN terminology has a separate third-party license; this Actor uses only food enforcement data, never restricted medical-device classifications. FDA does not warrant completeness or accuracy. Do not claim government endorsement.

### Quality controls

Run tests in the project root with:

```sh
python3 -m unittest discover -s tests -v
```

Tests include a real downloaded FDA record, a confirmed-text-and-lot candidate, conflicting lot and manufacturer, no match, changed source fingerprint, corrupt customer CSV/JSON, PII-field rejection and FDA archive completeness. ZIP test data under fixtures is never shipped in the Actor Docker image. Invalid or stale FDA datasets cause a safe failure without emitting misleading no_match output. No login, CAPTCHA bypass, secret source, proxy or API key is required.

# Actor input Schema

## `inventoryFiles` (type: `array`):

Upload exactly one authorized UTF-8 CSV or JSON inventory file. No personal information.

## `inventoryFileUrl` (type: `string`):

Alternative to uploading: temporary direct HTTPS URL to authorized CSV/JSON file.

## `inventoryRows` (type: `array`):

JSON array with row_id, product_name, and optional brand/manufacturer/lot/barcode/sku. Use only if not uploading a file.

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

Configure maxresults for matching.

## `maxMatchesPerRow` (type: `integer`):

Configure maxmatchesperrow for matching.

## `matchThreshold` (type: `number`):

Conservative confidence threshold; strong product-only clues can still be returned for free manual review.

## `maxDataAgeDays` (type: `integer`):

Configure maxdataagedays for matching.

## `previousResults` (type: `array`):

Optional FDA-only array \[{recallNumber, recordFingerprint}] from earlier run to flag changed FDA records. Do not include inventory/customer data.

## `inventoryStoreId` (type: `string`):

Choose a private Apify key-value store that contains the CSV or JSON file you uploaded. Grants the Actor READ-only access to the chosen store. Alternative to URL or inline rows.

## `inventoryRecordKey` (type: `string`):

Record key containing the inventory CSV/JSON file, e.g. INVENTORY.csv.

## Actor input object example

```json
{
  "inventoryRows": [
    {
      "row_id": "QA-DEMO-001",
      "product_name": "Rare Celestial Galactic Unicorn Breakfast Bubbles"
    }
  ],
  "maxResults": 50,
  "maxMatchesPerRow": 3,
  "matchThreshold": 0.75,
  "maxDataAgeDays": 21,
  "inventoryRecordKey": "INVENTORY.csv"
}
```

# Actor output Schema

## `matches` (type: `string`):

Per-row FDA candidate evidence, uncertainty, status, source URL, and FDA-only fingerprint.

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

FDA export date, record counts, input coverage, matching statistics, billable count.

# 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 = {
    "inventoryRows": [
        {
            "row_id": "QA-DEMO-001",
            "product_name": "Rare Celestial Galactic Unicorn Breakfast Bubbles"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("afkdigital/food-recall-inventory-matcher").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 = { "inventoryRows": [{
            "row_id": "QA-DEMO-001",
            "product_name": "Rare Celestial Galactic Unicorn Breakfast Bubbles",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/food-recall-inventory-matcher").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 '{
  "inventoryRows": [
    {
      "row_id": "QA-DEMO-001",
      "product_name": "Rare Celestial Galactic Unicorn Breakfast Bubbles"
    }
  ]
}' |
apify call afkdigital/food-recall-inventory-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/food-recall-inventory-matcher"
        }
    }
}
```

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/JLsxvSsX2OZoO2Ftm/builds/pZnBkhWEla8Yvsywx/openapi.json
