FDA Drug Labels Scraper (openFDA SPL Data)
Pricing
from $3.00 / 1,000 drug label delivereds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 hours ago
Last modified
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*"oropenfda.generic_name:"SILICEA". - Effective date filter:
effective_sincekeeps labels whoseeffective_timeis on or after the given ISO date. The source metadata fieldmeta.results.last_updatedshows 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+versionand only the first record of eachset_idper run, so the same label version is never billed twice. - Schema validation: every item is validated against
.actor/dataset_schema.jsonbefore 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}
| Field | Type | Description |
|---|---|---|
search | string | Optional openFDA search syntax. Example: openfda.brand_name:"*aspirin*". See the search parameter reference. |
effective_since | string (YYYY-MM-DD) | Optional lower bound on effective_time. |
maxResults | integer | Default 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
| Field | Type | Notes |
|---|---|---|
label_id | string | openFDA label id (SPL document id). Required. Deduplication key together with version. |
set_id | string | openFDA set id shared by all versions of the same label. Required. Only the first record of each set_id is delivered per run. |
version | string or null | Label version as recorded by openFDA. |
effective_time | string (YYYY-MM-DD) | Label effective date, normalized from openFDA YYYYMMDD. Required. |
brand_names | array of strings | Brand names from the label's openFDA block. |
generic_names | array of strings | Generic names from the label's openFDA block. |
manufacturers | array of strings | Labeler/manufacturer names. |
indications | string | Indications and usage text, joined into one string. Empty when the source has none. |
warnings | string | Warnings text, joined into one string. Empty when the source has none. |
url | string | Official openFDA API URL for this label record (Steadfetch common envelope). |
title | string or null | First brand name, otherwise the first generic name. |
scrapedAt | string | ISO 8601 UTC timestamp of the extraction. |
Billing and deduplication
The Actor uses Apify pay-per-event pricing with the two synthetic platform events:
| Event | Charged when | Price |
|---|---|---|
apify-actor-start | a run starts (synthetic, automatic) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute) |
apify-default-dataset-item | a 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+versionare emitted once per run. - Same label, multiple versions: only the first record of each
set_idis 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 installnpm test # lint + typecheck + offline Vitest suites (no credit)npm run publish:check # Steadfetch publish gatenpm run build && npm run verify:buildnpm run schemas:validatenpm 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/backoffsrc/extract.ts Pure normalization and deduplication (fixture-tested, no network)src/main.ts Entry point: fetch, validate, pushsrc/validate-output.ts Ajv validator shared by the run and the teststest/ 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.