FDA Complete Response Letter Monitor & API
Pricing
from $10.00 / 1,000 watch target checkeds
FDA Complete Response Letter Monitor & API
Search and monitor FDA Complete Response Letters (CRLs) by company or NDA/BLA. Get structured letter text, provenance and typed new-letter alerts from the official openFDA API. A source outage is never reported as no letters.
Pricing
from $10.00 / 1,000 watch target checkeds
Rating
0.0
(0)
Developer
Vadim Bezrukov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Watch companies and NDA/BLA application numbers for newly published FDA Complete Response Letters, or search the official openFDA CRL archive, without turning a source outage into “no letters”.
For regulatory-affairs and competitive-intelligence workflows, the Actor returns structured letters with OCR text, issuance dates and source provenance. Saved watch state distinguishes newly observed letters, historical additions and edits to previously seen records. Export JSON/CSV or pass typed events to your own webhook workflow. Coverage follows FDA's published archive and update cadence; this is not a real-time feed of every issued CRL.
The first monitor run for NDA 210852 (the public default) publishes a truthful BASELINE even when Changes only is on. A later scheduled run of the same watchlist is the reason to run it again: it should stay quiet when the archive is unchanged, and it should never claim a 2019 letter was issued today merely because it appeared in the API today.
Choose your workflow
| Goal | Settings | What you get |
|---|---|---|
| Watch named sponsors | monitor + companies | BASELINE on the first run, then typed new/retroactive/updated letters |
| Watch NDA / BLA numbers | monitor + applications | Same contract, matched on NDA 210852 including /Original 1 variants |
| Look up the archive once | search + filters | Newest matching letters, no monitoring state |
Start from a ready-to-copy Task: Monitor FDA CRLs for biotech companies, Track CRLs by NDA / BLA application, or Search the FDA Complete Response Letter database. Replace the public sample values with your own watchlist or search filters.
Quick start
Default Store prefill:
{"mode": "monitor","monitorKey": "default","applications": ["NDA 210852"],"changesOnly": true}
On 2026-09-13 that application had six letters in the official archive. The first run returns six BASELINE rows for one watch charge (about $0.01005 total). Repeat exactly this input: an unchanged archive returns one TARGET row with UNCHANGED, for the same watch charge. Keep monitorKey stable when scheduling daily or weekly.
Monitor new FDA Complete Response Letters
Set mode to monitor, keep monitorKey stable, and supply applications and/or companies. The Actor downloads the current official archive over HTTPS, matches each watch locally, and compares the result with the last successful observation in Key-Value Store. One bad watch does not wipe another watch’s baseline. A failed archive fetch leaves last-good state untouched.
Monitoring state persists across runs in the named store fda-crl-monitor-state in your Apify account. Keep the same monitorKey for repeat checks; use a different key for an independent watchlist or changed filtering scope. Run checks for the same key sequentially to avoid overlapping state writes. Incompatible saved state fails with STATE_INCOMPATIBLE and stays intact; use a new key to start over. Older builds saved state in each run's default store, so the first run after upgrading establishes a fresh baseline in the named store. Search does not open this store.
Search the FDA CRL archive
Set mode to search and use application numbers, company names and optional letter-type, approval-status or issuance-date filters. maxResults caps delivered letters; lower it to control the bill. When that cap hides remaining matches, each delivered row sets truncated to true. Case-insensitive text filters and application suffixes are evaluated locally against the complete bounded archive. If that archive cannot be fetched (ARCHIVE_TRUNCATED), the search fails closed. Search does not write monitoring state.
For a small archive lookup, use:
{"mode": "search", "filters": {"approvalStatuses": ["approved"]}, "maxResults": 5}
This returns up to five newest matching letters for at most $0.05005. Omit unused filter keys; dates must use YYYY-MM-DD.
Search delivers the newest matching letters first, including company searches. If every supplied watch is invalid, it returns only INVALID_INPUT rows without fetching the archive. Invalid application filters are rejected before source requests. Missing totals, totals changing between pages, and incomplete pagination are source failures, even for small archives.
What data do you get?
Each successful letter row includes application numbers, letter type and issuance date, approval status, company name, FDA approver identity, full letter text, file_name, a stable record_id, fingerprint, observed_at / scraped_at, and first_seen_at. The separate company mailing-address and addressee fields are dropped; full OCR text may still contain those details as part of the public letter.
letter_date is the date printed on the letter. first_seen_at is when this Actor first stored that record_id. They are not interchangeable.
Watch companies
Company matching is case-insensitive after punctuation is stripped. Amgen Inc. and Amgen, Inc. match. Pfizer Inc. does not match Pfizer Ireland Pharmaceuticals. There is no fuzzy or token-OR match. Curly apostrophes are normalized so Dr. Reddy’s and Dr. Reddy's are the same watch.
Watch NDA / BLA applications
Provide NDA 210852 or BLA 761215. Bare digits are rejected. A watch without a suffix matches /Original 1 and /S-05 variants of the same application number.
Detect new and retroactively published CRLs
A letter whose letter_date is on or after the previous successful meta.last_updated is NEW_CRL. A letter that appears for the first time whose issuance date is earlier than that previous source date is RETROACTIVE_CRL_ADDED. If issuance date or source last-updated is missing, the Actor emits NEW_SOURCE_RECORD rather than guessing. Updates to an already stored record_id are CRL_RECORD_UPDATED with previous/current field values.
Input
| Field | Purpose |
|---|---|
mode | monitor (default) or search |
applications | Up to 500 NDA/BLA/ANDA numbers |
companies | Up to 200 sponsor names |
filters | Optional letter type, approval status, issuance-date range |
monitorKey | Independent watchlist namespace |
changesOnly | After baseline, omit unchanged letters |
maxResults | Search-mode delivery cap (1–1000) |
openfdaApiKey | Optional; anonymous access is enough for normal use |
Output
Dataset views: New and changed CRLs, Complete Response Letters, Watch-target status. NO_RESULTS is a verified empty match, including openFDA’s HTTP 404 No matches found!. SOURCE_FAILED is used for timeouts, 429/5xx after retries, HTML, invalid JSON, pagination corruption, archive collapse, a truncated full-archive snapshot (ARCHIVE_TRUNCATED) and records that cannot be normalized (NORMALIZE_FAILED). Those two statuses are never collapsed.
A RUN_SUMMARY record in the default Key-Value Store gives target counts, source request/retry counts, baseline/new/updated counts, Dataset row count and sourceLastUpdated.
spendLimitReached means the remaining budget could not cover a requested event. The Actor emits a TARGET status row with PARTIAL / SPEND_LIMIT instead of the omitted letters, and monitoring state does not advance. Previously delivered and charged checks remain successful. Each watch is charged only after all its output rows are saved. A Dataset write failure is not charged for that watch. An unexpected charge rejection after delivery fails the run and retains state.
Scheduling and webhooks
Schedule the same Task daily or weekly with a fixed monitorKey. Dataset views only project columns; they do not hide rows. For a webhook or Dataset poll that should fire only on new letters, filter items where record_type is CHANGE and change_types contains NEW_CRL, RETROACTIVE_CRL_ADDED or CRL_RECORD_UPDATED. A later changesOnly run still writes one TARGET row with UNCHANGED per quiet watch, and the first run publishes BASELINE. A verified empty watch keeps last-good letter ids, so a later rematch is UNCHANGED rather than a false NEW_CRL. If an interrupted run already started Dataset or billing output, resurrection fails closed (DELIVERY_UNCERTAIN) and does not replay charges; start a new run for a fresh observation.
This is a copy-paste illustration, not a hosted integration promise.
- Save the monitor JSON above as a Task. In Apify Schedules, use
0 6 * * *in UTC for a daily 06:00 check and attach that Task. - Add an
ACTOR.RUN.SUCCEEDEDwebhook pointing to your n8n/Make URL. Readresource.defaultDatasetIdfrom the notification and fetch its items with your own Apify connection. InspectRUN_SUMMARYfirst: a run can finishSUCCEEDEDwith onlyUNCHANGEDwatches, or fail after writingSOURCE_FAILEDrows. - Keep Dataset rows where
record_typeisCHANGEandchange_typescontainsNEW_CRL,RETROACTIVE_CRL_ADDEDorCRL_RECORD_UPDATED. - Open
source_urland the letterfile_namefor the human check. Do not treat a CRL as a trading or medical conclusion.
API / MCP usage
Direct Actor MCP endpoint:
https://mcp.apify.com?tools=automa-flow/fda-complete-response-letter-monitor
After Store publication and indexing, anonymous MCP search can discover the Actor; execution requires the caller's authenticated access and budget. Hosted MCP execution has not yet been verified. Ask an agent to monitor NDA 210852 and return new or retroactively added letters. The Actor requests limited Apify permissions for Dataset and state storage; source collection uses HTTPS to api.fda.gov, with no browser or source login.
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("automa-flow/fda-complete-response-letter-monitor").call(run_input={"mode": "monitor","monitorKey": "default","applications": ["NDA 210852"],"changesOnly": True,},max_total_charge_usd=0.011,)for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["status"], item.get("change_types"), item.get("application_numbers"))
Pricing
Pay-per-event. Platform usage is included, not passed through. apify-default-dataset-item is off so Dataset rows are not billed twice.
| Event | When it is charged | Price |
|---|---|---|
apify-actor-start | Automatic run start (512 MB = one event) | $0.00005 |
watch-target-checked | Once per unique company or application successfully evaluated in monitor mode, including a verified empty match | $0.01 |
archive-record | Once per delivered search letter | $0.01 |
Retries, invalid watches, source failures, duplicate watches, duplicate search record ids and change types have no custom charge. The platform start fee still applies to failed and empty runs. A verified empty monitor watch is charged once; an empty search has no archive-record charge. A first default run (NDA 210852) costs about $0.01005: start plus one watch check, even if six BASELINE letters are written. A later quiet run of the same watch costs the same. Search of 50 letters costs about $0.50005.
Set maxTotalChargeUsd to at least $0.011 so the Store prefill can finish. That is a minimum budget, not a minimum bill.
Twenty watches cost $0.20005 per run (about $6.0015 for 30 daily runs). The input ceilings are 700 unique watches ($7.00005) or 1,000 search letters ($10.00005). The default $1 run cap covers 99 custom events plus start; raise it deliberately for larger jobs. Increasing maxResults alone does not raise that budget.
Failure semantics
| Outcome | Meaning |
|---|---|
SUCCESS | Verified letter or verified watch evaluation |
NO_RESULTS | Official API answered; zero matching letters |
SOURCE_FAILED | The source could not be verified, including ARCHIVE_TRUNCATED and NORMALIZE_FAILED. The Actor run fails after writing these rows. |
FAILED | That watch could not be evaluated locally; others still run |
INVALID_INPUT | That watch could not be parsed; others still run |
PARTIAL | Letter delivery omitted because the remaining spend limit cannot cover it |
Data source and limitations
Source: openFDA Complete Response Letters at https://api.fda.gov/transparency/crl.json. Observed 2026-09-13: 458 letters, meta.last_updated 2026-08-13, anonymous HTTPS JSON, no proxy or browser. Terms dedicate unmarked openFDA content to CC0 / public domain and allow commercial use. file_name is not unique; identity is file_name + letter_date + application numbers + letter_type. OCR text contains redactions and scan artifacts. The archive is incomplete relative to every CRL FDA has ever issued; FDA says it will keep adding letters and may publish historical batches.
This Actor is not a press-release catalyst feed. FDA warning-letter scrapers and ticker-mapped PDUFA calendars are different products.
FDA / medical disclaimer
This Actor is not affiliated with, endorsed by, or an official product of the U.S. Food and Drug Administration. Do not rely on openFDA or this Actor for medical care, investment decisions, approval predictions or compliance conclusions. A Complete Response Letter is correspondence about an application in its then-current form. It does not by itself prove a product is unsafe, that a company is non-compliant, or that approval will fail or succeed. Assume results are unvalidated and can change when FDA updates the dataset.
FAQ
Do I need an openFDA API key? No for normal watchlists. Anonymous use is currently 1,000 requests per IP per day. A free key raises that cap and is stored only as a secret input.
Why did a 2019 letter show up as new? If it was not in last-good state and its issuance date is before the previous source last_updated, the event is RETROACTIVE_CRL_ADDED, not “issued today”.
Does empty output mean there are no CRLs? Only a NO_RESULTS row after a successful source check means a verified empty match. SOURCE_FAILED means the check did not complete.
Why is truncated true? Search hit maxResults while more matching letters remain, or the search page itself was capped. A monitor or company-archive scan that cannot read the full archive fails instead of setting that flag.
Will you score deficiencies or predict approvals? No. That is out of scope for V1.