Regulatory Medical Recalls & Drug Safety Monitor avatar

Regulatory Medical Recalls & Drug Safety Monitor

Pricing

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

Go to Apify Store
Regulatory Medical Recalls & Drug Safety Monitor

Regulatory Medical Recalls & Drug Safety Monitor

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.

Pricing

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

Rating

0.0

(0)

Developer

Stefano Seggio

Stefano Seggio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 hours ago

Last modified

Categories

Share

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

FieldTypeDefaultDescription
sourcesarray (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.
maxItemsPerSourceinteger100Hard 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.
onlyNewbooleanfalseWhen 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.
dateRangestring (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.
fdaClassificationarray (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).
fdaApiKeystring (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

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

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

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

Example configuration — EMA-only broad sweep

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

Output Dataset Sample & Data Dictionary

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

FieldTypeDescription
recordSourcestringfda_enforcement or ema_dhpc — which regulator feed produced this record.
record_idstringStable, source-prefixed id (fda:<recall_number> or ema:<slug>:<dissemination_date>) — unique within this actor's own record space.
event_typestringSANCTION, NEW_LISTING, STATUS_CHANGE, UPDATED, or SNAPSHOT_NO_DIFF — see Technical Features above.
scraped_atstringISO-8601 timestamp of this extraction.
is_newboolean | nulltrue if not seen in a prior run (delta mode, this actor's own key-value store).
source_urlstring | nullDirect link to the source record.
recipient_or_defendant_namestring | nullFDA: 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_nativestring | nullFDA: event_id. EMA: atc_code_human (WHO ATC classification code).
value_nativestring | nullAlways null — neither source carries a monetary value field.
value_currencystring | nullAlways null.
value_usd_normalizednumber | nullAlways null.
effective_date_isostring | nullFDA: recall_initiation_date. EMA: dissemination_date.
publish_date_isostring | nullFDA: report_date. EMA: first_published_date.
category_or_typestring | nullFDA: product_type. EMA: dhpc_type.
status_or_estadostring | nullFDA: status (Ongoing/Terminated/Completed). EMA: regulatory_outcome (often empty -> null).
awarding_or_regulating_agencystring | nullThe regulating agency for this record.
jurisdictionstringUS or EU.
source_document_urlstring | nullAlways null for both sources — neither feed exposes a separate per-record document/PDF URL distinct from source_url.
reference_numberstring | nullFDA: recall_number. EMA: procedure_number (often empty -> null).
regulatoryDataDisclaimerstringMandatory, 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)

FieldTypeDescription
classificationstring | nullFDA Classification — Class I/II/III severity tier.
productDescriptionstring | nullProduct description.
reasonForRecallstring | nullReason for recall.
recallingFirmstring | nullRecalling firm.
distributionPatternstring | nullDistribution pattern.
voluntaryMandatedstring | nullVoluntary or mandated.
recallNumberstring | nullFDA recall number.
eventIdstring | nullFDA event ID.
citystring | nullCity.
statestring | nullState.
countrystring | nullCountry.

EMA-only extension fields (null on FDA records)

FieldTypeDescription
nameOfMedicinestring | nullName of medicine.
activeSubstancesstring | nullActive substances.
dhpcTypestring | nullDHPC type.
atcCodeHumanstring | nullATC code (human-readable, WHO classification).
therapeuticAreaMeshstring | nullTherapeutic area (MeSH).
procedureNumberstring | nullProcedure number.
regulatoryOutcomestring | nullRegulatory outcome.

Sample record — FDA

{
"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

{
"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

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)

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)

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:

EventPriceWhen it fires for this actor
result$0.001 per recordEvery 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.00005Once 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.