# Regulatory Medical Recalls & Drug Safety Monitor (`stefano_seggio/actor-22-drug-safety-recalls-monitor`) Actor

Combines the FDA's openFDA drug enforcement (recall) API and the EMA's DHPC safety-alert feed into one 18-field UMS stream, tagged by jurisdiction and agency, sorted newest-first, with a mandatory regulatory-data disclaimer on every record.

- **URL**: https://apify.com/stefano\_seggio/actor-22-drug-safety-recalls-monitor.md
- **Developed by:** [Stefano Seggio](https://apify.com/stefano_seggio) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 drug safety recall/alert records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Regulatory Medical Recalls & Drug Safety Monitor — Apify Store Overview

**Store URL:** https://apify.com/stefano\_seggio/actor-22-drug-safety-recalls-monitor
**Actor ID:** HJZvKxFUpZop6gIQ3
**Version:** 2.0

***

### Executive Summary & Business Use Case

Regulatory Medical Recalls & Drug Safety Monitor combines two genuinely open, publisher-sanctioned regulator feeds into one normalized stream: the **FDA's openFDA drug enforcement (recall) API** (`api.fda.gov`, the US Food and Drug Administration's own public enforcement database) and the **EMA's Direct Healthcare Professional Communications (DHPC) safety-alert JSON export** (the European Medicines Agency's feed of formal safety communications sent to prescribers and pharmacists across the EU). FDA recalls and EMA DHPCs use completely different native field names — `recalling_firm` and `classification` on one side, `name_of_medicine` and `dhpc_type` on the other — and this actor maps both into a single shared record shape, tagged by `jurisdiction` (`US`/`EU`) and `recordSource` (`fda_enforcement`/`ema_dhpc`), sorted newest-first, with a mandatory `regulatoryDataDisclaimer` string on every record stating the data is sourced directly from the named regulator's own public feed and is not independently medically verified or intended as clinical/consumer medical advice.

Three concrete, real-world use cases follow directly from what these two source feeds actually expose. First, **compliance and pharmacovigilance teams** at pharmaceutical, distribution, and pharmacy-chain companies can track ongoing FDA recalls and EMA safety communications for the specific products, manufacturers, or therapeutic areas (via `atcCodeHuman`/`therapeuticAreaMesh`) they are responsible for monitoring, filtered by FDA severity classification (Class I/II/III) when only the most serious recalls matter. Second, **market and competitive intelligence teams** can watch a named competitor's or category's recall activity across both the US and EU jurisdictions in one feed instead of maintaining two separate manual watch processes on two government websites with different update cadences and formats. Third, **data teams building a compliance dashboard or alerting pipeline** get clean, typed, disclaimer-tagged JSON they can pipe straight into a database or BI tool, instead of screen-scraping (or manually re-checking) `fda.gov` and `ema.europa.eu` on separate schedules.

What this actor deliberately does not claim is as important as what it does. Neither openFDA's enforcement records nor EMA's DHPCs carry a monetary value field, so every value-related field in the dataset (`value_native`, `value_currency`, `value_usd_normalized`) is always `null` — there is no procurement-spend or contract-value use case here, and this overview does not manufacture one. The data supports safety and compliance monitoring, not financial analysis.

***

### Technical Features & V2 Architecture Highlights

**Cross-run delta persistence via a named key-value store.** Per this actor's own `onlyNew` input description, enabling delta mode "persists a content fingerprint per record between runs (in this actor's own named key-value store)" — not the run-scoped, ephemeral `Actor.getValue()`/`Actor.setValue()` pattern, but a store that survives between scheduled runs. The CHANGELOG confirms `src/state.ts` was "already correctly using `Actor.openKeyValueStore()`" even before the v2.0 release, so — unlike a sibling actor in this fleet that needed a state-store migration fix — no such migration was needed here.

**A dual-fingerprint delta engine, not a flat seen-id list.** V2.0 replaced a plain "have I seen this id before" check with two separate fingerprints per record, computed in `src/fingerprint.ts`: a `statusFingerprint` covering just the status/regulatory-outcome field, and a `contentFingerprint` covering the record's other mutable fields. State is now shaped as `{ entries: Record<source, Record<recordId, { statusFingerprint, contentFingerprint, lastSeenAt }>>, lastRunAt }`, replacing v1's flat `{ seenIds: Record<source, string[]>, lastRunAt }`. This is a non-backward-compatible shape change by design: an old-shaped state is treated as absent rather than migrated, so an existing scheduled task's first v2.0 run simply re-baselines against the richer shape — the same pattern already used across this fleet's other v1-to-v2 upgrades.

**Real event types — five values, read directly from `dataset_schema.json`'s `event_type` field, not the fleet's generic four-event set:**

- `SANCTION` — first time this exact FDA enforcement record has been seen (preserved from v1's deliberate per-source naming, not replaced with a generic term).
- `NEW_LISTING` — first time this exact EMA DHPC record has been seen.
- `STATUS_CHANGE` — a repeat sighting where FDA `status` (Ongoing/Terminated/Completed) or EMA `regulatory_outcome` differs from the last time this actor saw the record.
- `UPDATED` — a repeat sighting where some other tracked field differs, but status/outcome did not.
- `SNAPSHOT_NO_DIFF` — a repeat sighting that is byte-for-byte identical to the last time it was seen; skipped from delivery when `onlyNew` is on.

**No `CLOSED` event, and this is a documented, deliberate omission, not a gap.** Per both the CHANGELOG's "Not added (and why)" section and the dataset schema's own field description: neither source is confirmed, by this actor's own live-verification standard, to ever remove a historical record — openFDA is a permanent enforcement database (a Terminated recall stays queryable, it doesn't disappear) and EMA DHPCs are permanent regulator communications once issued. Independently, this actor fetches a bounded, newest-first top-N window per source (`maxItemsPerSource`, default 100) rather than exhaustively walking either source's full historical register every run — and a trustworthy `CLOSED`/complete-census check requires exactly that kind of exhaustive walk, which this actor's bounded recency-window design does not attempt.

**`onlyNew` — what it actually does, per this actor's own input schema.** When enabled, the actor persists a content fingerprint per record between runs (in the named key-value store described above) and returns only records that are new since the last run, OR whose status (FDA `status` / EMA `regulatory_outcome`) or other tracked fields changed since they were last seen. Records that are byte-for-byte identical to last time (`SNAPSHOT_NO_DIFF`) are skipped entirely. This is a **behavior change disclosed in the v2.0 CHANGELOG, not a silent shift**: in v1, `onlyNew` meant only "never seen before"; in v2.0 it also delivers `STATUS_CHANGE` and `UPDATED` records, so a recall flipping from `Ongoing` to `Terminated` on an already-known record is still surfaced to a recurring monitor, instead of being silently suppressed just because the underlying recall "was seen before." Existing scheduled tasks using `onlyNew: true` will see more records delivered than before whenever a status or content change occurs.

**Hardened HTTP retry logic (a genuine v2.0 bug fix, not a new feature).** Before v2.0, `src/http.ts`'s retry logic excluded all 4xx status codes from its retry path, including 429 — meaning a rate-limited request from either openFDA or EMA failed immediately instead of backing off and retrying. Fixed in v2.0: 429 and 5xx responses are now explicitly retried with exponential backoff plus jitter (to avoid a thundering-herd retry if the actor is ever run concurrently across multiple schedules); genuine permanent client errors (400/404/etc.) are still correctly not retried. A `Retry-After` header, when the server sends one on a 429/503 (either the seconds form or an HTTP-date, per RFC 9110), is now honored as the authoritative delay instead of the computed backoff — covered by 6 dedicated tests in `test/http.test.ts`.

**Defensive date-range guard.** `src/dateUtils.ts`'s `isWithinDateRange` now carries the same `diffMs >= 0` guard already proven necessary on this fleet's `mendoza-compras-monitor` and `pba-tenders-monitor` — a naive one-sided check would let any future-dated record match every window. Not a live-observed bug on this actor (EMA `dissemination_date` is always a past publication date in practice), but the same bug class, fixed proactively.

**No baseline/backlog-floor pagination mechanism, and that's deliberate.** Some sibling actors in this fleet use a dual-floor mechanism to avoid draining a large historical backlog cap-by-cap across many runs during a cold-start baseline. This actor doesn't need it: both FDA and EMA are always fetched as a single bounded, newest-first top-N call per run (never a paginated walk of a full backlog), so there is no unbounded-backlog problem for a floor to solve — the bounded fetch is naturally its own floor.

**Field count — read directly from `dataset_schema.json`, not assumed.** The schema defines 20 fields shared by every record regardless of source (`recordSource`, `record_id`, `event_type`, `scraped_at`, `is_new`, `source_url`, `recipient_or_defendant_name`, `entity_identifier_native`, `value_native`, `value_currency`, `value_usd_normalized`, `effective_date_iso`, `publish_date_iso`, `category_or_type`, `status_or_estado`, `awarding_or_regulating_agency`, `jurisdiction`, `source_document_url`, `reference_number`, `regulatoryDataDisclaimer`), plus 11 FDA-only extension fields and 7 EMA-only extension fields — 38 fields total per record. (This actor's own `actor.json` description and README refer to it as "one 18-field UMS stream" / "the shared 18-field envelope"; the live `dataset_schema.json` shared-field count is actually 20, not 18 — see the Notes below.)

***

### Input Schema & JSON Configuration Example

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `sources` | array (items enum: `fda`, `ema`) | `["fda", "ema"]` | FDA = US openFDA drug enforcement (recall) API. EMA = EU Direct Healthcare Professional Communications (DHPC) safety-alert JSON export. Both are independent regulator-native feeds with different fields, combined into one Unified Master Schema stream. |
| `maxItemsPerSource` | integer | `100` | Hard cap on how many records to return per selected regulator this run, applied independently to FDA and EMA, both sorted newest-first. openFDA's own API caps a single request at 1000 results (verified live 2026-09-07); values above 1000 are paginated automatically via skip. |
| `onlyNew` | boolean | `false` | When enabled, persists a content fingerprint per record between runs (in this actor's own named key-value store) and returns only records that are new since the last run OR whose status (FDA status / EMA regulatory\_outcome) or other tracked fields changed since they were last seen — records that are byte-for-byte identical to last time are skipped. Recommended for recurring monitoring, so a status change on an already-known recall (e.g. Ongoing -> Terminated) is still surfaced, not silently suppressed just because the recall itself isn't brand new. Leave off for a full one-off extraction of every currently-visible record. |
| `dateRange` | string (enum: `24h`, `7d`, `30d`) | *(none)* | Optionally restrict results to records whose own date field (FDA report\_date; EMA dissemination\_date) falls within this window. Independent of onlyNew. |
| `fdaClassification` | array (items enum: `Class I`, `Class II`, `Class III`) | *(none)* | Restrict FDA results to one or more FDA recall classification tiers (Class I = most severe / reasonable probability of serious harm or death, down to Class III = least severe). Ignored for EMA records, which use their own dhpc\_type taxonomy instead (no equivalent tiering in the DHPC feed). |
| `fdaApiKey` | string (secret) | *(none)* | Optional. Without a key, openFDA allows 240 requests/minute and 1,000 requests/day per IP address (verified live against https://open.fda.gov/apis/authentication/ on 2026-09-07). With a free key (open.fda.gov signup), the daily cap rises to 120,000 requests/day per key, same 240/minute. This actor's own request volume is trivially within the unauthenticated limit for normal use; the key is offered only for high-frequency scheduled runs. |

#### Example configuration — default pull, both regulators

```json
{
  "sources": ["fda", "ema"],
  "maxItemsPerSource": 100
}
```

#### Example configuration — recurring delta monitor, high-severity FDA only

```json
{
  "sources": ["fda"],
  "maxItemsPerSource": 500,
  "onlyNew": true,
  "dateRange": "7d",
  "fdaClassification": ["Class I", "Class II"]
}
```

#### Example configuration — EMA-only broad sweep

```json
{
  "sources": ["ema"],
  "maxItemsPerSource": 1000,
  "dateRange": "30d"
}
```

***

### Output Dataset Sample & Data Dictionary

#### Shared envelope fields (present on every record, FDA or EMA)

| Field | Type | Description |
| --- | --- | --- |
| `recordSource` | string | `fda_enforcement` or `ema_dhpc` — which regulator feed produced this record. |
| `record_id` | string | Stable, source-prefixed id (`fda:<recall_number>` or `ema:<slug>:<dissemination_date>`) — unique within this actor's own record space. |
| `event_type` | string | `SANCTION`, `NEW_LISTING`, `STATUS_CHANGE`, `UPDATED`, or `SNAPSHOT_NO_DIFF` — see Technical Features above. |
| `scraped_at` | string | ISO-8601 timestamp of this extraction. |
| `is_new` | boolean | null | `true` if not seen in a prior run (delta mode, this actor's own key-value store). |
| `source_url` | string | null | Direct link to the source record. |
| `recipient_or_defendant_name` | string | null | FDA: `recalling_firm`. EMA: `name_of_medicine` (no marketing-authorisation-holder/company field exists in the DHPC feed, so the medicine itself is the named subject — never guessed). |
| `entity_identifier_native` | string | null | FDA: `event_id`. EMA: `atc_code_human` (WHO ATC classification code). |
| `value_native` | string | null | Always `null` — neither source carries a monetary value field. |
| `value_currency` | string | null | Always `null`. |
| `value_usd_normalized` | number | null | Always `null`. |
| `effective_date_iso` | string | null | FDA: `recall_initiation_date`. EMA: `dissemination_date`. |
| `publish_date_iso` | string | null | FDA: `report_date`. EMA: `first_published_date`. |
| `category_or_type` | string | null | FDA: `product_type`. EMA: `dhpc_type`. |
| `status_or_estado` | string | null | FDA: `status` (Ongoing/Terminated/Completed). EMA: `regulatory_outcome` (often empty -> null). |
| `awarding_or_regulating_agency` | string | null | The regulating agency for this record. |
| `jurisdiction` | string | `US` or `EU`. |
| `source_document_url` | string | null | Always `null` for both sources — neither feed exposes a separate per-record document/PDF URL distinct from `source_url`. |
| `reference_number` | string | null | FDA: `recall_number`. EMA: `procedure_number` (often empty -> null). |
| `regulatoryDataDisclaimer` | string | Mandatory, non-removable on every record: states the data is sourced directly from the named regulator's own public feed, is not independently medically verified by this actor, and is not intended as clinical or consumer medical advice. |

#### FDA-only extension fields (null on EMA records)

| Field | Type | Description |
| --- | --- | --- |
| `classification` | string | null | FDA Classification — Class I/II/III severity tier. |
| `productDescription` | string | null | Product description. |
| `reasonForRecall` | string | null | Reason for recall. |
| `recallingFirm` | string | null | Recalling firm. |
| `distributionPattern` | string | null | Distribution pattern. |
| `voluntaryMandated` | string | null | Voluntary or mandated. |
| `recallNumber` | string | null | FDA recall number. |
| `eventId` | string | null | FDA event ID. |
| `city` | string | null | City. |
| `state` | string | null | State. |
| `country` | string | null | Country. |

#### EMA-only extension fields (null on FDA records)

| Field | Type | Description |
| --- | --- | --- |
| `nameOfMedicine` | string | null | Name of medicine. |
| `activeSubstances` | string | null | Active substances. |
| `dhpcType` | string | null | DHPC type. |
| `atcCodeHuman` | string | null | ATC code (human-readable, WHO classification). |
| `therapeuticAreaMesh` | string | null | Therapeutic area (MeSH). |
| `procedureNumber` | string | null | Procedure number. |
| `regulatoryOutcome` | string | null | Regulatory outcome. |

#### Sample record — FDA

```json
{
  "recordSource": "fda_enforcement",
  "record_id": "fda:F-1442-2026",
  "event_type": "STATUS_CHANGE",
  "scraped_at": "2026-09-08T14:00:00.000Z",
  "is_new": false,
  "source_url": "https://api.fda.gov/drug/enforcement.json?search=recall_number:%22F-1442-2026%22",
  "recipient_or_defendant_name": "Meridian Pharma Labs LLC",
  "entity_identifier_native": "80234567",
  "value_native": null,
  "value_currency": null,
  "value_usd_normalized": null,
  "effective_date_iso": "2026-08-14",
  "publish_date_iso": "2026-08-22",
  "category_or_type": "Drugs",
  "status_or_estado": "Terminated",
  "awarding_or_regulating_agency": "U.S. Food and Drug Administration (FDA)",
  "jurisdiction": "US",
  "source_document_url": null,
  "reference_number": "F-1442-2026",
  "regulatoryDataDisclaimer": "This record is sourced directly from the named regulator's own public enforcement/safety-communication feed and has not been independently medically verified by this Actor. It is not intended as clinical or consumer medical advice.",
  "classification": "Class I",
  "productDescription": "Metformin Hydrochloride Extended-Release Tablets, 500 mg, 100-count bottles",
  "reasonForRecall": "Presence of N-Nitrosodimethylamine (NDMA) above the acceptable daily intake limit",
  "recallingFirm": "Meridian Pharma Labs LLC",
  "distributionPattern": "Nationwide, including Puerto Rico",
  "voluntaryMandated": "Voluntary: Firm Initiated",
  "recallNumber": "F-1442-2026",
  "eventId": "80234567",
  "city": "Fort Lauderdale",
  "state": "FL",
  "country": "United States",
  "nameOfMedicine": null,
  "activeSubstances": null,
  "dhpcType": null,
  "atcCodeHuman": null,
  "therapeuticAreaMesh": null,
  "procedureNumber": null,
  "regulatoryOutcome": null
}
```

#### Sample record — EMA

```json
{
  "recordSource": "ema_dhpc",
  "record_id": "ema:tepzolam-oral-suspension:2026-08-30",
  "event_type": "NEW_LISTING",
  "scraped_at": "2026-09-08T14:00:00.000Z",
  "is_new": true,
  "source_url": "https://www.ema.europa.eu/en/documents/dhpc/direct-healthcare-professional-communication-dhpc-tepzolam-oral-suspension",
  "recipient_or_defendant_name": "Tepzolam Oral Suspension",
  "entity_identifier_native": "N05CD08",
  "value_native": null,
  "value_currency": null,
  "value_usd_normalized": null,
  "effective_date_iso": "2026-08-30",
  "publish_date_iso": "2026-08-30",
  "category_or_type": "Risk of medication error due to concentration confusion",
  "status_or_estado": null,
  "awarding_or_regulating_agency": "European Medicines Agency (EMA)",
  "jurisdiction": "EU",
  "source_document_url": null,
  "reference_number": null,
  "regulatoryDataDisclaimer": "This record is sourced directly from the named regulator's own public enforcement/safety-communication feed and has not been independently medically verified by this Actor. It is not intended as clinical or consumer medical advice.",
  "classification": null,
  "productDescription": null,
  "reasonForRecall": null,
  "recallingFirm": null,
  "distributionPattern": null,
  "voluntaryMandated": null,
  "recallNumber": null,
  "eventId": null,
  "city": null,
  "state": null,
  "country": null,
  "nameOfMedicine": "Tepzolam Oral Suspension",
  "activeSubstances": "Midazolam",
  "dhpcType": "Risk minimisation communication",
  "atcCodeHuman": "N05CD08",
  "therapeuticAreaMesh": "Conscious Sedation",
  "procedureNumber": null,
  "regulatoryOutcome": null
}
```

***

### Multi-language Integration Snippets

#### cURL

```bash
curl "https://api.apify.com/v2/acts/stefano_seggio~actor-22-drug-safety-recalls-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": ["fda", "ema"],
    "maxItemsPerSource": 250,
    "onlyNew": true,
    "dateRange": "7d",
    "fdaClassification": ["Class I", "Class II"]
  }'
```

#### Python (apify-client)

```python
from apify_client import ApifyClient

client = ApifyClient(token="YOUR_APIFY_TOKEN")

run_input = {
    "sources": ["fda", "ema"],
    "maxItemsPerSource": 250,
    "onlyNew": True,
    "dateRange": "7d",
    "fdaClassification": ["Class I", "Class II"],
}

run = client.actor("stefano_seggio/actor-22-drug-safety-recalls-monitor").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"[{item['event_type']}] {item['recordSource']} - {item['recipient_or_defendant_name']} - {item['status_or_estado']}")
```

#### Node.js (apify-client)

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const runInput = {
    sources: ['fda', 'ema'],
    maxItemsPerSource: 250,
    onlyNew: true,
    dateRange: '7d',
    fdaClassification: ['Class I', 'Class II'],
};

const run = await client.actor('stefano_seggio/actor-22-drug-safety-recalls-monitor').call(runInput);

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
    console.log(`[${item.event_type}] ${item.recordSource} - ${item.recipient_or_defendant_name} - ${item.status_or_estado}`);
}
```

***

### Pricing Model Explanation

This actor bills per result event ("Pay per event"), with platform usage costs already included in each event's price:

| Event | Price | When it fires for this actor |
| --- | --- | --- |
| `result` | $0.001 per record | Every record delivered to the dataset — FDA or EMA, any `event_type` (`SANCTION`, `NEW_LISTING`, `STATUS_CHANGE`, or `UPDATED`). A single flat rate; there is no two-tier split by source or by event type. |
| `apify-actor-start` | $0.00005 | Once per run, regardless of which sources are selected or how the run is configured. |

Unlike some sibling actors in this fleet that bill at two different rates depending on whether an extra, genuinely expensive per-record lookup succeeded, this actor has no such second tier because it doesn't need one: every field on every record — FDA or EMA, base envelope or source-specific extension fields — comes back in the same single, flat JSON response per source per run. There is no separate detail-fetch step whose success or failure would justify charging some records more than others, so FDA and EMA records, and every event type among them, are billed identically at $0.001. This is confirmed in the actor's own v2.0 CHANGELOG, which explicitly notes "No pricing/monetization change — the live-configured PPE price ($0.001/record) was already correct and is untouched by this release."

For delta monitoring, enabling `onlyNew: true` means an already-known, unchanged record (`SNAPSHOT_NO_DIFF`) is filtered out before the dataset push happens at all — it is never delivered and never billed at $0; it simply never becomes a chargeable `result` event in the first place. Only records that are new, status-changed, or otherwise updated are pushed, so a warm recurring monitor with a stable underlying dataset costs a small fraction of a cold, full-extraction run.

**Example run cost:** the default configuration (`sources: ["fda", "ema"]`, `maxItemsPerSource: 100`) can return up to 200 records — 100 per source — costing roughly 200 × $0.001 + $0.00005 ≈ **$0.20 per run**, matching the actor's own README-quoted estimate for its default input. A daily `onlyNew: true` monitor that typically surfaces only a handful of new or changed records per day (a realistic outcome once the delta engine has a baseline from its first run) costs a small fraction of that per subsequent run — e.g. 5 changed records ≈ 5 × $0.001 + $0.00005 ≈ **$0.005 per run**, well under a dollar a month at daily cadence.

***

### Notes on source-file consistency

Everything above is grounded directly in `.actor/actor.json`, `.actor/input_schema.json`, `.actor/dataset_schema.json`, `README.md`, and `CHANGELOG.md` as they exist in this actor's repo. One discrepancy is worth flagging for whoever maintains this actor next: `actor.json`'s own `description` field and `README.md` both describe the output as "one 18-field UMS stream" / "the shared 18-field envelope," but the live `dataset_schema.json` actually defines **20** fields shared across every record before the FDA-only and EMA-only extension fields begin (`recordSource` through `regulatoryDataDisclaimer`, inclusive). This overview describes the schema as it actually is (20 shared fields, 38 total) rather than repeating the "18-field" figure from the marketing copy, since the task's own accuracy standard for this document is the schema file, not the description text.

# Actor input Schema

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

FDA = US openFDA drug enforcement (recall) API. EMA = EU Direct Healthcare Professional Communications (DHPC) safety-alert JSON export. Both are independent regulator-native feeds with different fields, combined into one Unified Master Schema stream.

## `maxItemsPerSource` (type: `integer`):

Hard cap on how many records to return per selected regulator this run, applied independently to FDA and EMA, both sorted newest-first. openFDA's own API caps a single request at 1000 results (verified live 2026-09-07); values above 1000 are paginated automatically via skip.

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

When enabled, persists a content fingerprint per record between runs (in this actor's own named key-value store) and returns only records that are new since the last run OR whose status (FDA status / EMA regulatory\_outcome) or other tracked fields changed since they were last seen - records that are byte-for-byte identical to last time are skipped. Recommended for recurring monitoring, so a status change on an already-known recall (e.g. Ongoing -> Terminated) is still surfaced, not silently suppressed just because the recall itself isn't brand new. Leave off for a full one-off extraction of every currently-visible record.

## `dateRange` (type: `string`):

Optionally restrict results to records whose own date field (FDA report\_date; EMA dissemination\_date) falls within this window. Independent of onlyNew.

## `fdaClassification` (type: `array`):

Restrict FDA results to one or more FDA recall classification tiers (Class I = most severe / reasonable probability of serious harm or death, down to Class III = least severe). Ignored for EMA records, which use their own dhpc\_type taxonomy instead (no equivalent tiering in the DHPC feed).

## `fdaApiKey` (type: `string`):

Optional. Without a key, openFDA allows 240 requests/minute and 1,000 requests/day per IP address (verified live against https://open.fda.gov/apis/authentication/ on 2026-09-07). With a free key (open.fda.gov signup), the daily cap rises to 120,000 requests/day per key, same 240/minute. This actor's own request volume is trivially within the unauthenticated limit for normal use; the key is offered only for high-frequency scheduled runs.

## Actor input object example

```json
{
  "sources": [
    "fda",
    "ema"
  ],
  "maxItemsPerSource": 100,
  "onlyNew": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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("stefano_seggio/actor-22-drug-safety-recalls-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("stefano_seggio/actor-22-drug-safety-recalls-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 '{}' |
apify call stefano_seggio/actor-22-drug-safety-recalls-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stefano_seggio/actor-22-drug-safety-recalls-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/HJZvKxFUpZop6gIQ3/builds/NTc9Z8FBzPfb0vT6i/openapi.json
