FDA Complete Response Letter Monitor & API avatar

FDA Complete Response Letter Monitor & API

Pricing

from $10.00 / 1,000 watch target checkeds

Go to Apify Store
FDA Complete Response Letter Monitor & API

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

Vadim Bezrukov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

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

GoalSettingsWhat you get
Watch named sponsorsmonitor + companiesBASELINE on the first run, then typed new/retroactive/updated letters
Watch NDA / BLA numbersmonitor + applicationsSame contract, matched on NDA 210852 including /Original 1 variants
Look up the archive oncesearch + filtersNewest 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

FieldPurpose
modemonitor (default) or search
applicationsUp to 500 NDA/BLA/ANDA numbers
companiesUp to 200 sponsor names
filtersOptional letter type, approval status, issuance-date range
monitorKeyIndependent watchlist namespace
changesOnlyAfter baseline, omit unchanged letters
maxResultsSearch-mode delivery cap (1–1000)
openfdaApiKeyOptional; 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.

  1. 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.
  2. Add an ACTOR.RUN.SUCCEEDED webhook pointing to your n8n/Make URL. Read resource.defaultDatasetId from the notification and fetch its items with your own Apify connection. Inspect RUN_SUMMARY first: a run can finish SUCCEEDED with only UNCHANGED watches, or fail after writing SOURCE_FAILED rows.
  3. Keep Dataset rows where record_type is CHANGE and change_types contains NEW_CRL, RETROACTIVE_CRL_ADDED or CRL_RECORD_UPDATED.
  4. Open source_url and the letter file_name for 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 os
from apify_client import ApifyClient
client = 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.

EventWhen it is chargedPrice
apify-actor-startAutomatic run start (512 MB = one event)$0.00005
watch-target-checkedOnce per unique company or application successfully evaluated in monitor mode, including a verified empty match$0.01
archive-recordOnce 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

OutcomeMeaning
SUCCESSVerified letter or verified watch evaluation
NO_RESULTSOfficial API answered; zero matching letters
SOURCE_FAILEDThe source could not be verified, including ARCHIVE_TRUNCATED and NORMALIZE_FAILED. The Actor run fails after writing these rows.
FAILEDThat watch could not be evaluated locally; others still run
INVALID_INPUTThat watch could not be parsed; others still run
PARTIALLetter 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.