FDA Drug Labels Scraper (openFDA SPL Data) avatar

FDA Drug Labels Scraper (openFDA SPL Data)

Pricing

from $3.00 / 1,000 drug label delivereds

Go to Apify Store
FDA Drug Labels Scraper (openFDA SPL Data)

FDA Drug Labels Scraper (openFDA SPL Data)

Extract US drug label records (brand, generic, manufacturer, route, ingredients, indications, dosage, warnings) from the official openFDA SPL API. Filter by date, keyword or manufacturer. For pharmacovigilance, drug data pipelines and formulary research. Schema-validated JSON; no browser, no login.

Pricing

from $3.00 / 1,000 drug label delivereds

Rating

0.0

(0)

Developer

Steadfetch

Steadfetch

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Categories

Share

steadfetch/openfda-drug-labels-extractor

Extract structured US drug label records from the official openFDA drug label API. Use native openFDA search syntax or an effective date, and get validated JSON with label ids, versions, brand and generic names, manufacturers, indications and warnings. No browser, no login, no CAPTCHA.

What it does

The Actor calls the official, keyless openFDA API and returns one dataset item per normalized drug label (SPL document).

  • Native openFDA search: pass any query supported by the API, for example openfda.brand_name:"*aspirin*" or openfda.generic_name:"SILICEA".
  • Effective date filter: effective_since keeps labels whose effective_time is on or after the given ISO date. The source metadata field meta.results.last_updated shows when openFDA last refreshed the dataset.
  • Pagination: 1,000 records per request with limit/skip, newest effective date first, up to 5,000 results per run.
  • Billing-safe deduplication: one item per label_id+version and only the first record of each set_id per run, so the same label version is never billed twice.
  • Schema validation: every item is validated against .actor/dataset_schema.json before it reaches the dataset. Invalid items fail the run; nothing unvalidated is ever pushed.
  • Rate-limit friendly: 429 responses are retried with exponential backoff. If the limit persists, the Actor stops pagination gracefully and returns the labels collected so far; a rate limit is never charged or reported as a run error.

openFDA is a public, keyless API released as public domain (CC0): see the openFDA terms and license. The drug label endpoint contains no personally identifiable information. This Actor maps product-level label metadata only: it never collects patient, prescriber or contact data, and it does not scrape the openFDA website.

effective_time is normalized from the openFDA YYYYMMDD value to ISO 8601 (YYYY-MM-DD, JSON Schema date). Multi-part source text is joined into one string; list fields stay arrays; unknown optional values are null or an empty array.

Input

Minimal run, the fixture used during development:

{
"search": "openfda.generic_name:\"SILICEA\"",
"maxResults": 10
}

Incremental run since a given effective date:

{
"effective_since": "2026-01-01",
"maxResults": 1000
}
FieldTypeDescription
searchstringOptional openFDA search syntax. Example: openfda.brand_name:"*aspirin*". See the search parameter reference.
effective_sincestring (YYYY-MM-DD)Optional lower bound on effective_time.
maxResultsintegerDefault 100, maximum 5000. Every delivered label is one billable event.

All fields are optional. With no filters the Actor pages through all labels, newest effective date first.

Example output

{
"url": "https://api.fda.gov/drug/label.json?search=id:%22ca7bbcc8-2354-375c-e053-2995a90a72a0%22",
"title": "SILICEA",
"scrapedAt": "2026-10-20T13:00:00.000Z",
"label_id": "ca7bbcc8-2354-375c-e053-2995a90a72a0",
"set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a",
"version": "2",
"effective_time": "2021-09-02",
"brand_names": ["SILICEA"],
"generic_names": ["SILICEA"],
"manufacturers": ["Rxhomeo Private Limited d.b.a. Rxhomeo, Inc"],
"indications": "INDICATIONS Condition listed above or as directed by the physician",
"warnings": "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of a health care professional."
}

The warnings text above is shortened for readability. The committed fixture in test/fixtures/valid-items.json is the exact, complete output for the real records in test/fixtures/search-silicea.json.

Output fields

FieldTypeNotes
label_idstringopenFDA label id (SPL document id). Required. Deduplication key together with version.
set_idstringopenFDA set id shared by all versions of the same label. Required. Only the first record of each set_id is delivered per run.
versionstring or nullLabel version as recorded by openFDA.
effective_timestring (YYYY-MM-DD)Label effective date, normalized from openFDA YYYYMMDD. Required.
brand_namesarray of stringsBrand names from the label's openFDA block.
generic_namesarray of stringsGeneric names from the label's openFDA block.
manufacturersarray of stringsLabeler/manufacturer names.
indicationsstringIndications and usage text, joined into one string. Empty when the source has none.
warningsstringWarnings text, joined into one string. Empty when the source has none.
urlstringOfficial openFDA API URL for this label record (Steadfetch common envelope).
titlestring or nullFirst brand name, otherwise the first generic name.
scrapedAtstringISO 8601 UTC timestamp of the extraction.

Billing and deduplication

The Actor uses Apify pay-per-event pricing with the two synthetic platform events:

EventCharged whenPrice
apify-actor-starta run starts (synthetic, automatic)0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute)
apify-default-dataset-itema normalized, schema-validated label is saved to the dataset (synthetic, automatic)0.003 USD per label = 3.00 USD per 1,000 labels

There is no custom per-item event and no Actor.charge() call: the platform already charges apify-default-dataset-item per pushed item, so adding another event would bill users twice.

Duplicate handling follows the billing contract:

  • Same label version: records with the same label_id+version are emitted once per run.
  • Same label, multiple versions: only the first record of each set_id is emitted per run. The API is queried newest first, so the newest version is the one delivered; older versions are not billed again.
  • Never charged: errors, retries, 429 rate limits, empty pages, malformed records and duplicates do not push an item.

Pricing

The result price is 3.00 USD per 1,000 labels (apify-default-dataset-item at 0.003 USD). It sits inside the direct-competitor band for openFDA drug label extractors on the Apify Store (2-50 USD per 1,000) and inside Steadfetch's 1-10 USD per 1,000 band.

Measured cost: a local benchmark returned 5,000 labels in 6.5 seconds using 5 API requests, with 188 MB peak RSS and a 256 MB memory setting. At Apify's 0.20 USD per compute unit (1 CU = 1 GB of RAM for 1 hour), that run costs about 0.00009 USD, or roughly 0.00002 USD per 1,000 labels. Adding the run-start event, cost stays below 0.0001 USD per 1,000 labels; even with 10x platform overhead it remains below 0.001 USD per 1,000, far under the 0.15 USD per 1,000 estimate in the spec. No paid proxy is used. Margin is above 99 %.

Works on the Apify free plan — about 1,666 drug labels for the $5 monthly credit ((5.00 USD − 0.0002 USD run-start at the default 4 GB memory) ÷ 0.003 USD per label).

Development

npm install
npm test # lint + typecheck + offline Vitest suites (no credit)
npm run publish:check # Steadfetch publish gate
npm run build && npm run verify:build
npm run schemas:validate
npm run smoke # local Apify run (no platform credit)

Fixtures are real openFDA responses saved under test/fixtures/. The default test suite never touches the network and never spends Apify credit. npm run smoke executes the Actor locally; a platform run is only needed once, with the minimal input above.

Project structure

.actor/actor.json Actor definition (payPerEvent, dataset schema, output schema)
.actor/input_schema.json INPUT: search, effective_since, maxResults
.actor/dataset_schema.json Item JSON Schema (fields) + table view for the Store
.actor/pay_per_event.json PPE pricing (two synthetic platform events)
src/fetch.ts openFDA client: query builder, pagination, retry/backoff
src/extract.ts Pure normalization and deduplication (fixture-tested, no network)
src/main.ts Entry point: fetch, validate, push
src/validate-output.ts Ajv validator shared by the run and the tests
test/ Offline fixtures and Vitest suites

FAQ

Do I need an API key? No. openFDA is keyless. There is no secret to configure and no key is accepted through the input.

Does it need a browser? No. openFDA returns structured JSON, so plain HTTP is roughly 10x cheaper than a browser-based Actor.

Does it return personal data? No. Only product-level label metadata is mapped. Patient, prescriber and contact fields are never read or emitted.

What does effective_since do? It adds effective_time:[YYYYMMDD TO 99991231] to the openFDA query, so only labels effective on or after that date are returned. Use it for incremental extractions; meta.results.last_updated tells you when the source dataset was last refreshed.

How does pagination work? The Actor requests 1,000 records per call with limit/skip, sorted by effective_time:desc, until maxResults is reached or the result set is exhausted.

What happens with duplicate labels? The same label_id+version is emitted once per run, and only the first record of each set_id is kept. Duplicates are counted in the run log and are never charged.

What happens when openFDA rate-limits the Actor? 429 responses are retried with exponential backoff (honoring Retry-After). If the limit persists after all retries, pagination stops gracefully and the labels already collected are returned. No rate-limited request produces a billable item.

What happens when openFDA has no matches? The API returns a 404 "No matches found"; the Actor treats it as an empty result set, not as a failure, and emits nothing.

Is the output schema stable? Yes. New fields are additive and the required fields in .actor/dataset_schema.json do not change. effective_time is always an ISO 8601 date.

Maintenance

This Actor is tested daily: automated runs query the official openFDA API with a minimal input and check that the output still matches the published schema. Failures are reported as issues in the Steadfetch Actors repository.