# Food Supplier & Regulatory Intelligence (`vamsi-krishna/food-supplier-regulatory-intelligence`) Actor

US food supplier/product screening Actor: one-shot scan of a roster against FDA openFDA food enforcement and USDA FSIS recalls plus Safe & Suitable Ingredients. Emits evidence-linked supplier_exposure and ingredient_reference records. Not a general web scraper; not medical or legal advice.

- **URL**: https://apify.com/vamsi-krishna/food-supplier-regulatory-intelligence.md
- **Developed by:** [Vamsi Krishna](https://apify.com/vamsi-krishna) (community)
- **Categories:** Automation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $35.00 / 1,000 supplier profile screeneds

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

## Food Supplier & Regulatory Intelligence

**Apify Actor identity:** US food **supplier/product screening** in **scan** or **monitor** mode against **FDA openFDA food enforcement** and **USDA FSIS** recalls / Safe & Suitable Ingredients. Official APIs only — **not** a general web scraper, and **not** medical or legal advice.

### What does Food Supplier & Regulatory Intelligence do?

This Actor **screens a US food supplier and product roster** against official **FDA openFDA food enforcement** and **USDA FSIS** recall and ingredient APIs, then writes **evidence-linked** Dataset records (`supplier_exposure`, `ingredient_reference`) plus a run summary. Use **`mode=scan`** for a one-shot screen of the lookback window, or **`mode=monitor`** for scheduled change-detection against a namespaced baseline. It is a **screening support** tool for importers, distributors, and manufacturers — **not** a regulatory certification, legal opinion, or guarantee of complete recall coverage.

Official sources used:

- [FDA openFDA Food Enforcement](https://open.fda.gov/apis/food/enforcement/)
- [USDA FSIS recalls API](https://www.fsis.usda.gov/fsis/api/recall/v/1)
- [USDA FSIS Safe & Suitable Ingredients (Directive 7120.1 Table 2)](https://www.fsis.usda.gov/fsis/api/fsis_directive7120_1/table/2)

### Why use Food Supplier & Regulatory Intelligence?

Use it when you need a repeatable, API-friendly way to check whether roster firms or products appear in recent US recall/enforcement data — without inventing evidence or collapsing severity into name-match confidence.

**Highlights**

- Screens against **FDA food enforcement**, **FSIS recalls**, and **FSIS Safe & Suitable Ingredients** in one run
- Deterministic matching: exact identifiers → exact name → aliases → fuzzy + context, with explicit confidence levels
- **Regulatory severity is independent of match confidence** (a Class I recall with a weak name match stays a weak match)
- Every alert-like Dataset item carries an official **`sourceUrl`** when the source provides one
- **Monitor mode** diffs against a persisted named-KV baseline and emits only **new** or **materially changed** exposures
- Runs on Apify with scheduling, API access, Dataset/KV storage, and spend limits via pay-per-event

**Audience:** food importers, distributors, manufacturers, and developers integrating supplier risk workflows.

### What data can it extract?

| Field | Type | Description |
| --- | --- | --- |
| `recordType` | string | `supplier_exposure` (recalls/enforcement) or `ingredient_reference` (FSIS ingredients) |
| `source` | string | `FDA_FOOD_ENFORCEMENT`, `USDA_FSIS_RECALLS`, or `USDA_FSIS_INGREDIENTS` |
| `officialRecordId` | string | Stable official id (e.g. FDA `recall_number`, FSIS recall number) |
| `supplierMatch` | object | Matched roster supplier, match type, confidence, reasons |
| `productMatch` | object | Optional matched roster product / brand |
| `regulatorySeverity` | string | Mapped regulatory class/risk (independent of match confidence) |
| `recall` | object | Firm, product, reason, status, dates, distribution |
| `ingredient` | object | Safe & Suitable substance / reference notes (ingredient hits only) |
| `evidence.sourceUrl` | string | Link back to the official record or API evidence |
| `evidence.matchedFields` | array | Which fields contributed to the match |
| `detectedAt` | string | UTC timestamp when the Actor emitted the record |
| `changeType` | string | Monitor only: `new`, `material_change`, or `baseline` (null in scan) |
| `currentFingerprint` / `previousFingerprint` | object | Monitor material fingerprints (classification, status, reason, matchedProducts, recordType) |
| `changedFields` | array | Fingerprint field names that differ (`material_change` only) |
| `stateNamespace` | string | Monitor namespace that owns the named-KV baseline |

Key-value store (default run store): `SCAN_SUMMARY` (counts, truncation, `sourceErrors`, `sourceFreshness`, and for monitor: `changeTypeCounts`, `baselineEstablished`, `stateNamespace`, `namedStore`) and optional source snapshots. Monitor baselines live in a **separate named store** `fsri-state-{namespace}` (keys `BASELINE`, `META`) — not only in the run’s default store.

### How to screen suppliers against FDA and FSIS data

1. Open the Actor in [Apify Console](https://console.apify.com/) and go to the **Input** tab.
2. Set `region` to `US` and `mode` to `scan` (or `monitor` for change-detection — see below).
3. Choose sources (FDA food enforcement, FSIS recalls, and/or FSIS ingredients).
4. Paste your **suppliers** and **products** roster (ids, legal names, aliases, optional FEI/UPC).
5. Set `lookbackDays`, `matchThreshold`, and `maxRecords` as needed.
6. For monitor: set a stable non-`default` `stateNamespace` (one per customer/watchlist).
7. Start the run. Download results from the Dataset (JSON, CSV, Excel, etc.) and inspect `SCAN_SUMMARY` in the run’s key-value store.

You can also start runs from the Actor **API** tab (or any Apify client) with the same JSON input.

#### How to use monitor mode

Monitor compares each run’s matches to a persisted baseline and pushes only **new** or **materially changed** exposures.

1. Set `mode` to `monitor` and choose a stable non-`default` `stateNamespace` (one per customer/watchlist).
2. Keep the **same roster** and **same** `stateNamespace` on every subsequent run.
3. First run with `emitBaselineMatches: false` (default): writes `BASELINE` + `META` into named store `fsri-state-{namespace}` and pushes **0** Dataset items; `SCAN_SUMMARY.baselineEstablished` is `true`.
4. Later runs: unchanged matches are suppressed (`SCAN_SUMMARY.changeTypeCounts.unchanged`). New official actions or material fingerprint edits appear as `changeType: "new"` or `"material_change"`.
5. To dump the first-run match set into the Dataset instead of a silent baseline, set `emitBaselineMatches: true` (items tagged `changeType: "baseline"`).

Monitor **rejects** empty namespaces and the literal `default`. Isolation is **namespace-only**: do not share namespaces across tenants. The named store `fsri-state-{namespace}` is created in the **Actor user’s** Apify account.

#### Schedule monitor on Apify

Use an [Apify Schedule](https://docs.apify.com/platform/schedules) with a **stable** `stateNamespace` and the **same roster** on every tick. Example daily cron (UTC):

- Cron: `0 6 * * *` (every day at 06:00 UTC)
- Actor input (illustrative — fill `suppliers` / `products` with your roster):

```json
{
  "region": "US",
  "mode": "monitor",
  "stateNamespace": "acme-watchlist",
  "emitBaselineMatches": false,
  "lookbackDays": 365,
  "sources": [
    "FDA_FOOD_ENFORCEMENT",
    "USDA_FSIS_RECALLS",
    "USDA_FSIS_INGREDIENTS"
  ],
  "suppliers": [],
  "products": [],
  "matchThreshold": "strong",
  "maxRecords": 1000
}
```

Keep `stateNamespace` and the supplier/product roster identical across scheduled runs so the baseline stays meaningful.

### How much will it cost?

Pricing uses Apify **pay-per-event (PPE)**. The Actor charges the custom event **`supplier-profile-screened`** once per supplier successfully screened in a run (scan or monitor — including silent baseline runs), with spend-limit stop when the platform reports the charge limit reached.

Event prices are configured in **Apify Console** for this Actor — this README does not invent public unit prices. Compute units may also apply depending on run size and platform plan. Check the Actor's pricing tab in Console for current rates and free-tier allowances.

### Input

See the **Input** tab for the full schema. Important fields:

| Field | Notes |
| --- | --- |
| `region` | `US` only in this MVP |
| `mode` | `scan` (one-shot) or `monitor` (baseline diff / change-detection) |
| `lookbackDays` | How far back to include FDA/FSIS recall/enforcement records |
| `sources` | Multiselect of FDA / FSIS recalls / FSIS ingredients |
| `suppliers` / `products` | Roster objects with ids, names, aliases, optional identifiers |
| `matchThreshold` | Emission floor for match confidence (default strong / legacy `85`) |
| `maxRecords` | Cap on Dataset items (monitor: cap applies to **pushed deltas**); truncation in `SCAN_SUMMARY` |
| `openFdaApiKey` | Optional secret; raises openFDA rate limits |
| `stateNamespace` | Required for monitor: unique tenant/watchlist id; **not** empty or `default`. Opens `fsri-state-{namespace}` |
| `emitBaselineMatches` | Monitor first run: `false` = silent baseline (0 pushes); `true` = emit all current matches as `baseline` |

### Output

You can download the Dataset in JSON, HTML, CSV, or Excel. Example `supplier_exposure` item (simplified):

```json
{
  "recordType": "supplier_exposure",
  "source": "FDA_FOOD_ENFORCEMENT",
  "officialRecordId": "F-0001-2026",
  "supplierMatch": {
    "name": "Example Food Ingredients Inc",
    "supplierId": "sup-001",
    "matchType": "exact",
    "matchConfidence": 1.0,
    "reasons": ["Exact firm name match"]
  },
  "regulatorySeverity": "Class I",
  "recall": {
    "firm": "Example Food Ingredients Inc",
    "product": "Example Protein Product",
    "reason": "Undeclared allergen (milk)."
  },
  "evidence": {
    "sourceUrl": "https://api.fda.gov/food/enforcement.json?search=recall_number:%22F-0001-2026%22",
    "matchedFields": ["firm"]
  }
}
```

Ingredient hits use `recordType: "ingredient_reference"` and never carry recall severity.

**Monitor deltas** add nullable fields on pushed items:

| `changeType` | Meaning |
| --- | --- |
| `new` | Stable id absent from the prior baseline |
| `material_change` | Fingerprint fields differ (see `changedFields`, `previousFingerprint`, `currentFingerprint`) |
| `baseline` | First-run emit when `emitBaselineMatches` is true |

Unchanged matches are **not** pushed. Material fingerprint fields: `classification`, `status`, `reason`, `matchedProducts`, `recordType`.

### Tips and limitations

- **US coverage only.** Canada/EU sources are out of scope for this MVP.
- **FDA openFDA food enforcement** updates on a roughly **weekly** cadence (`meta.last_updated`). Very fresh FDA actions may lag. Check **`SCAN_SUMMARY.sourceFreshness`** for per-source cadence / as-of timestamps.
- **FSIS recalls** are fetched as a **full corpus** per run, then filtered client-side by lookback. Prefer caching/snapshots for large repeated scans. Freshness for the FSIS corpus is also reported under `sourceFreshness`.
- **FSIS / Akamai:** FSIS JSON APIs often return **HTTP 403** without browser-like `User-Agent` and `Sec-Fetch-*` headers. This Actor sends those polite defaults. Residual IP/TLS or WAF blocks still appear under `SCAN_SUMMARY.sourceErrors` — never as fabricated evidence.
- **Monitor isolation:** one `stateNamespace` per customer/watchlist; named store lives in the Actor user’s account. Do not share namespaces. If a source hard-fails with zero records, prior baseline ids for that source are retained (fail-closed merge) so a later healthy fetch can still surface news.
- Raise precision by supplying **legal names, aliases, FEI, UPC/GTIN**, and linked products; fuzzy firm matches without context are not treated as confirmed supplier risk.
- Lower `maxRecords` / shorter `lookbackDays` to limit run size; PPE charging stops further screening when the spend limit is reached.
- Matching quality gates in CI apply only to the **labeled fixture set** in `tests/fixtures/labeled_matching_cases.json` — not a claim of production accuracy.

### FAQ, Disclaimers, and Support

#### Is this a regulatory certification?

No. Output is **screening support** for your own due diligence. It does **not** certify supplier safety, compliance, or fitness for a regulated purpose. Consult qualified regulatory or legal advisors for decisions that require certification. This disclaimer applies equally to **scan** and **monitor** output.

#### Does severity equal match confidence?

No. **`regulatorySeverity`** (e.g. Class I) describes the official action. **`supplierMatch.matchConfidence`** describes how strongly the official record relates to *your* roster entry. A severe recall with a weak name match is still a weak match.

#### Why might FDA results look behind?

openFDA food enforcement is typically refreshed about **weekly**. Use `lookbackDays` and inspect **`SCAN_SUMMARY.sourceFreshness`** (and related source timestamps) when timing matters.

#### Why do FSIS calls sometimes fail?

FSIS endpoints sit behind Akamai. Clients without compatible headers often get 403; this Actor sets defaults, but network/WAF blocks can remain. Failures are recorded in `sourceErrors` without inventing `sourceUrl` evidence.

#### How does monitor / schedule mode work?

Set `mode` to `monitor` with a stable non-`default` `stateNamespace`. The first run establishes a baseline in named KV store `fsri-state-{namespace}` (silent unless `emitBaselineMatches` is true). Later runs push only **new** or **materially changed** exposures (`changeType` `new` | `material_change`). Schedule the Actor on Apify with the same namespace and roster. Unchanged matches are suppressed; see `SCAN_SUMMARY.changeTypeCounts`.

#### Matching quality

Automated tests assert high-confidence (exact/strong) **precision ≥ 95% on the labeled fixture set**, that labeled positive alert-like outputs include `sourceUrl`, and that dedupe keys still suppress duplicate `(source, officialRecordId, supplier)` emissions. Those metrics are **fixture-scoped** and must not be marketed as production accuracy.

#### Personal data and ethics

This Actor queries **public official regulatory APIs** and matches them to the roster **you** provide. Do not submit personal data you are not entitled to process. If results could contain personal data from official notices, handle them under applicable privacy laws (including GDPR where relevant) and only with a legitimate purpose.

#### Support

- Use the Actor **Issues** tab on Apify Store for bugs and feature requests
- Use the **API** tab for programmatic runs and Dataset access
- Review `SCAN_SUMMARY` / `sourceErrors` before treating an empty Dataset as “all clear” (especially after a silent monitor baseline)

# Actor input Schema

## `region` (type: `string`):

Regulatory region to screen against. MVP supports US only (FDA openFDA food enforcement + USDA FSIS).

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

Run mode. Use scan for a one-shot roster screen against the lookback window. Use monitor for change-detection against a persisted named-KV baseline (requires a non-empty, non-default stateNamespace).

## `lookbackDays` (type: `integer`):

How many days of historical FDA/FSIS recall and enforcement records to include when scanning (ingredient table is not date-windowed the same way).

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

Official US data sources to query. FDA_FOOD_ENFORCEMENT and USDA_FSIS_RECALLS produce supplier_exposure records; USDA_FSIS_INGREDIENTS produces ingredient_reference records only.

## `suppliers` (type: `array`):

Supplier/manufacturer roster to match against recalling firms and related official fields. Provide legal/trade names, aliases, and optional FDA establishment identifiers — required for useful supplier_exposure hits.

## `products` (type: `array`):

Product roster to screen for product-level matches and ingredient_reference lookups. Include brand, manufacturer linkage, ingredient names, and optional GTIN/UPC/lot identifiers.

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

Minimum fuzzy-match score (0–100) required to keep a candidate match. Exact and alias matches are always retained. Match confidence is independent of regulatory severity.

## `maxRecords` (type: `integer`):

Maximum number of supplier_exposure and ingredient_reference records to push to the dataset (0 = unlimited). Truncation is reported in SCAN_SUMMARY.

## `openFdaApiKey` (type: `string`):

Optional openFDA API key for higher rate limits on FDA_FOOD_ENFORCEMENT. Leave empty to use anonymous quotas. Stored encrypted when provided.

## `emitBaselineMatches` (type: `boolean`):

Monitor first-run behavior when no baseline exists yet. If false (default), write a silent baseline and push zero dataset items. If true, push all current matches marked as baseline on the first monitor run. Ignored for mode=scan.

## `stateNamespace` (type: `string`):

Named key-value isolation namespace for monitor baselines (opens store fsri-state-{namespace}). Required for mode=monitor: must not be empty or the literal default (use a unique tenant/watchlist id). Scan may use default. Pattern: starts with a letter or digit, then 1–63 letters, digits, dots, underscores, or hyphens (^\[A-Za-z0-9]\[A-Za-z0-9.\_-]{1,63}$).

## Actor input object example

```json
{
  "region": "US",
  "mode": "scan",
  "lookbackDays": 365,
  "sources": [
    "FDA_FOOD_ENFORCEMENT",
    "USDA_FSIS_RECALLS",
    "USDA_FSIS_INGREDIENTS"
  ],
  "suppliers": [
    {
      "id": "sup-001",
      "name": "Example Food Ingredients Inc",
      "aliases": [
        "Example Ingredients"
      ],
      "country": "US",
      "fdaEstablishmentId": ""
    }
  ],
  "products": [
    {
      "id": "prod-001",
      "name": "Organic Protein Powder",
      "brand": "Example Nutrition",
      "manufacturer": "Example Food Ingredients Inc",
      "supplierId": "sup-001",
      "ingredients": [
        "Whey protein",
        "Cocoa powder"
      ],
      "gtin": "",
      "upc": "",
      "lotNumber": ""
    }
  ],
  "matchThreshold": 85,
  "maxRecords": 1000,
  "openFdaApiKey": "",
  "emitBaselineMatches": false,
  "stateNamespace": "default"
}
```

# Actor output Schema

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

Dataset items: supplier_exposure for FDA openFDA / USDA FSIS recall-enforcement roster matches; ingredient_reference for FSIS Safe & Suitable Ingredients hits. Monitor mode pushes only new / material_change / baseline deltas with changeType, fingerprints, and stateNamespace. Each alert-like item should include evidence.sourceUrl when the source provides one.

## `keyValueStore` (type: `string`):

Default run key-value store: SCAN_SUMMARY (counts, truncation, sourceErrors, sourceFreshness; monitor also changeTypeCounts, baselineEstablished, stateNamespace, namedStore), STATE (abort/progress), and snapshot-\* official source payloads. Named monitor baselines (BASELINE/META) live in fsri-state-{namespace} in the Actor user account.

# 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 = {
    "suppliers": [
        {
            "id": "sup-001",
            "name": "Example Food Ingredients Inc",
            "aliases": [
                "Example Ingredients"
            ],
            "country": "US",
            "fdaEstablishmentId": ""
        }
    ],
    "products": [
        {
            "id": "prod-001",
            "name": "Organic Protein Powder",
            "brand": "Example Nutrition",
            "manufacturer": "Example Food Ingredients Inc",
            "supplierId": "sup-001",
            "ingredients": [
                "Whey protein",
                "Cocoa powder"
            ],
            "gtin": "",
            "upc": "",
            "lotNumber": ""
        }
    ],
    "openFdaApiKey": "",
    "stateNamespace": "default"
};

// Run the Actor and wait for it to finish
const run = await client.actor("vamsi-krishna/food-supplier-regulatory-intelligence").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 = {
    "suppliers": [{
            "id": "sup-001",
            "name": "Example Food Ingredients Inc",
            "aliases": ["Example Ingredients"],
            "country": "US",
            "fdaEstablishmentId": "",
        }],
    "products": [{
            "id": "prod-001",
            "name": "Organic Protein Powder",
            "brand": "Example Nutrition",
            "manufacturer": "Example Food Ingredients Inc",
            "supplierId": "sup-001",
            "ingredients": [
                "Whey protein",
                "Cocoa powder",
            ],
            "gtin": "",
            "upc": "",
            "lotNumber": "",
        }],
    "openFdaApiKey": "",
    "stateNamespace": "default",
}

# Run the Actor and wait for it to finish
run = client.actor("vamsi-krishna/food-supplier-regulatory-intelligence").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 '{
  "suppliers": [
    {
      "id": "sup-001",
      "name": "Example Food Ingredients Inc",
      "aliases": [
        "Example Ingredients"
      ],
      "country": "US",
      "fdaEstablishmentId": ""
    }
  ],
  "products": [
    {
      "id": "prod-001",
      "name": "Organic Protein Powder",
      "brand": "Example Nutrition",
      "manufacturer": "Example Food Ingredients Inc",
      "supplierId": "sup-001",
      "ingredients": [
        "Whey protein",
        "Cocoa powder"
      ],
      "gtin": "",
      "upc": "",
      "lotNumber": ""
    }
  ],
  "openFdaApiKey": "",
  "stateNamespace": "default"
}' |
apify call vamsi-krishna/food-supplier-regulatory-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vamsi-krishna/food-supplier-regulatory-intelligence"
        }
    }
}
```

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/zMYdehkXxcSfWYr7Q/builds/N9qout7oo3wctbWh9/openapi.json
