Clinical Trials & FDA API — ClinicalTrials.gov + openFDA avatar

Clinical Trials & FDA API — ClinicalTrials.gov + openFDA

Pricing

from $0.30 / 1,000 trial returneds

Go to Apify Store
Clinical Trials & FDA API — ClinicalTrials.gov + openFDA

Clinical Trials & FDA API — ClinicalTrials.gov + openFDA

Query ClinicalTrials.gov v2 and openFDA from one Actor. Search 600,000+ registered studies by condition, drug, sponsor, phase, status or location, and get FDA drug and device recalls, labels, NDC entries, approvals, 510(k) clearances and adverse-event reports for the same drug. No API key.

Pricing

from $0.30 / 1,000 trial returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Give it a condition, a drug, a sponsor or a device name. Get back 600,000+ registered clinical studies from ClinicalTrials.gov API v2 and records from fourteen openFDA datasets — drug and device recalls, labels, approvals, 510(k) clearances, the NDC directory and adverse-event reports — in one table with one schema.

No API key. No login. No browser. Both sources are free, public, keyless US federal APIs, and one run answers "every trial of semaglutide, plus every FDA label, recall, approval and adverse-event report that names it" without writing two integrations.

From $0.0005 per trial and $0.0003 per FDA record, and every diagnostic row is free.

{
"ok": true,
"rowType": "trial",
"nctId": "NCT04368728",
"title": "Study to Describe the Safety, Tolerability, Immunogenicity, and Efficacy of RNA Vaccine Candidates Against COVID-19 in Healthy Individuals",
"status": "COMPLETED",
"statusVerifiedDate": "2026-03",
"phases": ["PHASE2", "PHASE3"],
"studyType": "INTERVENTIONAL",
"leadSponsor": "BioNTech SE",
"sponsorClass": "INDUSTRY",
"collaborators": [{ "name": "Pfizer", "class": "INDUSTRY" }],
"conditions": ["SARS-CoV-2 Infection", "COVID-19"],
"meshConditions": ["COVID-19"],
"interventions": [{ "type": "BIOLOGICAL", "name": "BNT162b1" }],
"enrollment": 46969,
"enrollmentType": "ACTUAL",
"masking": "TRIPLE",
"startDate": "2020-04-29",
"primaryCompletionDate": "2023-02-10",
"lastUpdatedAt": "2026-03-25",
"hasResults": true,
"locationsCount": 175,
"countries": ["United States", "Argentina", "Brazil", "Germany", "South Africa", "Turkey (Türkiye)"],
"primaryOutcomesCount": 64,
"minAge": "12 Years",
"stdAges": ["CHILD", "ADULT", "OLDER_ADULT"],
"sourceUrl": "https://clinicaltrials.gov/study/NCT04368728"
}

Try it in 30 seconds

Input — this is the Store prefill; paste it and run:

{
"conditions": ["type 2 diabetes"],
"statuses": ["RECRUITING"],
"maxTrials": 25,
"fdaEndpoints": ["drug/enforcement"],
"drugNames": ["semaglutide"],
"maxFdaRecords": 25
}

Three HTTP requests, about fifteen seconds, $0.021, no key.

Output — one trial row per study and one fda-record row per FDA record, in one table; the fields you will use most are rowType, nctId, title, status, phases and leadSponsor on a trial, endpoint, recordId, recordDate and title on an FDA record (full list under Output reference). Anything that could not be read comes back as a free diagnostic row (ok: false, errorType, error) instead of a charge.

Price — $0.50 per 1,000 trials and $0.30 per 1,000 FDA records on the FREE tier (+$0.001 per run); aggregate rows, summary rows, every diagnostic row, and a query that matched nothing are all free. Pay-per-event, no API key, no browser, limited permissions — works over the Apify MCP server (mcp.apify.com) and with agentic (x402) payments.

From codeclient.actor("insight.solutions/clinical-trials-fda-api").call(run_input={"conditions": ["type 2 diabetes"], "maxTrials": 25}) with apify-client, or POST https://api.apify.com/v2/acts/insight.solutions~clinical-trials-fda-api/run-sync-get-dataset-items.

Three more that show the shape of the thing:

// A sponsor's late-stage pipeline
{ "sponsors": ["Novo Nordisk"], "phases": ["PHASE3"], "statuses": ["RECRUITING"], "maxTrials": 200 }
// A device clearance and recall feed for the current year
{ "fdaEndpoints": ["device/510k", "device/recall"], "fdaDateFrom": "2026-01-01", "maxFdaRecords": 500 }
// A drug's top 100 reported reactions — 100 free rows, $0.001 for the whole run
{
"fdaEndpoints": ["drug/event"],
"drugNames": ["semaglutide"],
"fdaCount": ["patient.reaction.reactionmeddrapt.exact"],
"maxFdaRecords": 0
}

Two sources, one dataset

ClinicalTrials.gov API v2openFDA
What it holds602,735 registered studies, worldwideDrug and device recalls, SPL labels, approvals, 510(k)s, PMAs, the NDC directory, device classifications, and adverse-event reports for drugs, devices, food and animals
Row typetrialfda-record
Search bycondition, intervention, sponsor, lead sponsor, title, outcome, NCT id, status, phase, study type, location or radius, start date, last updatedrug name, device name, date window, raw openFDA expression
Price$0.0005$0.0003
Endpoints used/studies, /studies/<nctId>, /versionthe fourteen <endpoint>.json datasets
Key needednono (an optional free key raises the limits)

rowType also takes aggregate (a free count= facet term), summary (one free per-source roll-up) and diagnostic (a free explanation). Every row carries all 222 columns, null where they do not apply, so the export is one rectangular table however the run was configured.

Coverage and freshness, as numbers

ClinicalTrials.gov: 602,735 studies, measured from GET /api/v2/stats/size on 2026-09-15. Every run reads GET /api/v2/version and puts the registry's own apiVersion and dataTimestamp on the summary row, so the freshness claim is a fact from the source rather than a promise from us.

openFDA publishes a last_updated per dataset and they are not the same. It is on every fda-record row as endpointLastUpdated. Measured 2026-09-15:

EndpointData as ofBehind by
drug/label2026-09-12days
device/recall2026-09-12days
drug/ndc2026-09-11days
drug/drugsfda2026-09-11days
drug/enforcement2026-09-09days
device/enforcement2026-09-09days
food/enforcement2026-09-09days
device/event (MAUDE)2026-09-08a week
device/510k2026-09-07a week
device/pma2026-09-07a week
device/classification2026-09-07a week
drug/event (FAERS)2026-07-30six weeks
food/event (CAERS)2026-07-07ten weeks
animalandveterinary/event2026-07-02eleven weeks

The three adverse-event files lag by six weeks to a quarter, and that is the single most useful thing to know before building a monitor on them. Concretely: on 2026-09-15, drug/event?search=receivedate:[20260801 TO 20260901] returned no matches at all. The same query across [20260101 TO 20260901] returned tens of thousands. Enforcement and device endpoints are current within a week.

Input reference

ClinicalTrials.gov

FieldTypeDefaultWhat it does
conditionsstring[][]Condition or disease. Several entries are OR-ed; a phrase is quoted for you
interventionsstring[][]Drug, device, procedure or behavioural intervention
termsstring[][]Free text across the whole record; also accepts raw Essie expressions
sponsorsstring[][]Lead sponsor or collaborator
leadSponsorsstring[][]Lead sponsor only
titlesstring[][]Words in the brief or official title
outcomesstring[][]Words in the outcome measures
nctIdsstring[][]Look up specific studies. Ignores the other search fields
statusesenum[][]14 values, RECRUITING through WITHHELD
phasesenum[][]EARLY_PHASE1, PHASE1PHASE4, NA
studyTypesenum[][]INTERVENTIONAL, OBSERVATIONAL, EXPANDED_ACCESS
locationNamestringnullA place name matched against study sites (Boston, Texas, Germany)
locationobjectnullA radius: {"lat":40.7128,"lon":-74.0060,"radiusMiles":50}
updatedSincedatenullRegistry record last updated on or after
startDateFrom / startDateTodatenullStudy start window
sortenumLastUpdatePostDate:descAlso start date, enrolment, primary completion, relevance
maxTrialsinteger1000 means everything the filters allow
trialFieldsenumsummaryfull adds the complete raw study record, free
includeResultsbooleanfalseThe only setting that costs an extra request per study
maxResultFetchesinteger25Ceiling on those extra requests
maxLocationsPerTrialinteger50locationsCount always reports the real number

openFDA

FieldTypeDefaultWhat it does
fdaEndpointsenum[][]Which of the fourteen datasets to search
drugNamesstring[][]Brand, generic or substance name
deviceNamesstring[][]Trade or generic device name
fdaSearchstringnullRaw openFDA search= expression, AND-ed on
fdaDateFrom / fdaDateTodatenullApplied to each endpoint's own date field
fdaCountstring[][]Up to 20 fields to aggregate, and at most 20 count requests per run — free aggregate rows
maxFdaRecordsinteger100Per endpoint. 0 fetches none, which is how you get aggregates only
fdaFullTextbooleanfalseKeep full label text (rows can exceed 400 KB)
fdaRawbooleantrueKeep the trimmed source record in raw
openFdaApiKeysecret stringnullOptional. Raises the limits; everything works without it

Shared: includeSummary (default true), maxConcurrency (2), maxRunSecs (240), proxyConfiguration ({"useApifyProxy": true}).

Output reference

A trial row carries the whole study: identity and titles, status and the month the sponsor last verified it, phases, study type, design (allocation, model, masking, who was masked), enrolment and whether it is actual or estimated, every date with its ACTUAL/ESTIMATED type beside it, conditions and keywords plus the registry's derived MeSH terms, interventions, sponsor and collaborators, eligibility criteria and age range, sites (capped, with the true locationsCount kept), countries, contacts, outcome measures and counts, references and PubMed ids, and unpostedEvents when the registry has recorded that results are late.

A fda-record row:

{
"ok": true,
"rowType": "fda-record",
"endpoint": "drug/enforcement",
"recordId": "D-0815-2026",
"recordIdType": "recall_number",
"recordDate": "2026-09-09",
"recordDateField": "report_date",
"endpointLastUpdated": "2026-09-09",
"title": "Clindamycin Injection USP in 5% Dextrose, 900 mg per 50 mL…",
"recallNumber": "D-0815-2026",
"recallClassification": "Class II",
"recallStatus": "Ongoing",
"recallReason": "CGMP Deviations",
"recallingFirm": "Baxter Healthcare Corporation",
"firmCity": "Deerfield",
"firmState": "IL",
"firmCountry": "United States",
"distributionPattern": "US Nationwide; Puerto Rico; and Chile.",
"voluntaryMandated": "Voluntary: Firm initiated",
"brandNames": ["CLINDAMYCIN PHOSPHATE"],
"applicationNumbers": ["ANDA208084"],
"productNdcs": ["0338-3410", "0338-3612", "0338-3814"],
"reportedDates": {
"recallInitiationDate": "2026-08-06",
"centerClassificationDate": "2026-08-28",
"terminationDate": null
}
}

Free row types. aggregate — one term of a count= facet, with its rank and the facet total. summary — one per source: status, phase, sponsor, country and intervention-type breakdowns over the trials the run returned, the median enrolment, the registry's dataTimestamp, and how many records matched upstream per FDA endpoint. diagnostic — one per thing that could not be read or filter that could not be applied, with an errorType from no-results, not-found, invalid-input, unsupported-filter, api-key-required, rate-limit, forbidden, blocked, timeout, budget-exhausted, upstream-shape, network, http, parse.

Pricing

Pay per event. No subscription, no per-request charge, no usage add-on.

EventFREEBRONZESILVERGOLD
Run started (actor-start)$0.001$0.001$0.001$0.001
Clinical trial returned (trial)$0.0005$0.0005$0.0004$0.0003
FDA record returned (fda-record)$0.0003$0.0003$0.00024$0.00018

$0.50 per 1,000 trials · $0.30 per 1,000 FDA records.

You ask forYou getYou pay
The example above25 trials + 25 FDA records + 2 free summaries$0.021
1,000 trials for one sponsor1,000 trials$0.501
1,000 FAERS reports for one drug1,000 FDA records$0.301
A drug's top 100 reported reactions100 free aggregate rows$0.001
200 trials + 100 records across 4 FDA endpoints200 trials + 400 FDA records$0.221

What you are never charged for

  • aggregate rows — a count= facet is up to 100 terms for one request.
  • summary rows.
  • Every diagnostic row, whatever the errorType.
  • A query that matched nothing: openFDA's 404 NOT_FOUND, or ClinicalTrials.gov's {"totalCount": 0, "studies": []}.
  • A filter an endpoint cannot apply — a date window on device/classification, a drug name on a device endpoint — and every other unsupported-filter row.
  • Rows that could not be written because your run budget or the time budget ran out.
  • Anything at all on a run that got no usable answer from either API. The start fee is billed only after the first successful response and the first paid row, so an outage costs nothing and the run finishes FAILED.

Rate limits and the optional API key

openFDA, without a key: 240 requests a minute and 1,000 a day, per IP address. Pages hold at most 100 records — ask for more and the API answers 403 API_KEY_MISSING, so this Actor never asks.

openFDA, with a free key from https://open.fda.gov/apis/authentication/: 240 a minute and 120,000 a day, per key, and pages hold 1,000. Put it in openFdaApiKey (a secret field; it is stripped from every queryUrl before a row is written). Nothing in this Actor requires it.

The keyless quota is counted per IP, and the Apify datacenter proxy shares exits. That is the one place the default proxy setting is a liability rather than hygiene. For heavy openFDA work without a key, clear proxyConfiguration — both APIs answer unproxied, and ClinicalTrials.gov was nine times faster that way in testing (64 ms against 566 ms).

ClinicalTrials.gov publishes no rate limit and sends no rate-limit headers. This Actor holds itself to 3 requests a second and pauses 250–600 ms between pages of one query.

No response from either API tells us how much quota is left, so the Actor self-limits: without a key it stops after 200 openFDA requests in a run and says so in a free rate-limit row.

This is registry data, not medical advice

These are public registry and regulatory records, reproduced as published. Nothing here is medical advice and nothing here is a safety finding. openFDA's own terms say to assume all results are unvalidated and not to rely on them for decisions about medical care.

Adverse-event reports — FAERS (drug/event), MAUDE (device/event), CAERS (food/event) and the veterinary file — are voluntary, unverified reports. The FDA states that submitting one is not an admission that the product caused the event, that the reports have not been scientifically or otherwise verified as to a cause-and-effect relationship, and that they cannot be used to estimate how often something happens or to estimate risk. A report naming a drug does not mean the drug caused anything. Duplicate reports exist: recordVersion and recordDuplicate are on every row so you can see them, and a single report can name dozens of drugs — the one you searched for is often not the first.

Trial registry records are submitted by sponsors, not verified by the NIH or the National Library of Medicine. statusVerifiedDate tells you the month the sponsor last confirmed the status, and a study can sit at RECRUITING long after it stopped. Results are posted late or not at all; unpostedEvents is on the row when the registry has recorded that.

Contact names, phone numbers and email addresses on trial rows are published by the registry as the study's public contact points and are reproduced unchanged, including the placeholder addresses some sponsors submit (xxxxxxxxxx@xxx.com appears in the registry itself). Adverse-event records are stripped of free-text patient narrative and of manufacturer-contact address blocks before they are written. No personal data is inferred, enriched or added.

Recall classes are hazard classifications, not verdicts: Class I a reasonable probability of serious harm or death, Class II temporary or reversible harm, Class III unlikely to cause harm.

Limitations

  • The upstream format may change. Both APIs are versioned and stable, but a new module or a renamed field would show up as nulls in a column rather than as an error. Diagnostic rows carry upstream-shape when a response is not the shape we parse.
  • filter.geo selects studies, not sites. A 50-mile radius around New York returns studies with at least one site in range; their locations array is untouched. The top hit for "cancer near New York" has 279 sites worldwide, the first of them in Alabama. locationsCount is on the row so you can see it.
  • A 404 from openFDA cannot distinguish "no matches" from a field-name typo. The body is byte-identical for both. If you use fdaSearch, check your field names against https://open.fda.gov/apis/.
  • skip paging stops at 25,000 records. Above that the Actor uses openFDA's search_after cursor, which the two methods being incompatible means is chosen before the first request — set maxFdaRecords above 25,000 and the whole walk uses it.
  • device/classification has no date field, so fdaDateFrom/fdaDateTo are declined there with a free diagnostic rather than silently ignored.
  • fields= is not used. See the FAQ.
  • Not covered in v1: non-US registries (EU CTIS, ISRCTN, the WHO ICTRP), FDA drug shortages, warning letters, the Orange Book, CPSC and USDA recalls. Posted-results parsing is limited to four counts and the document list.

FAQ

Why does one request return 100 trials with everything on them? Because the ClinicalTrials.gov list endpoint returns the complete protocolSection and derivedSection for every study — verified across all 50 studies of a captured page. There is no per-study detail fetch to pay for, which is why trial sits at $0.0005 and why trialFields: "full" costs nothing extra.

Why does the Actor not send fields=? Because fields= returns the same nested structure, pruned — not a flat row — so the parser has to walk the same paths either way, and the byte saving is about 20%. The field-name to path mapping is also non-obvious (Phasephases[], EnrollmentCountenrollmentInfo.count), and the *DateStruct wrappers lose their type. One shape, parsed once, is the better trade.

Why is the drug I searched for not the first entry in drugNames? openFDA matches any drug on an adverse-event report. A report returned for OZEMPIC may list LANTUS SOLOSTAR first because the patient was taking both. All the drugs on the report are in drugNames, brandNames and genericNames (capped at 25), rather than one being picked and the rest quietly dropped.

Why is my drug label truncated? Unless fdaFullText is on, each text section is cut to 5,000 characters and the raw HTML *_table sections are dropped — a single SPL record runs 180–404 KB, of which 30 KB can be one HTML table. textTruncated and truncatedFields say what was cut, and labelSections lists every section the label has, including the dropped ones. Turn fdaFullText on to keep it all.

Recall lot lists are capped at 2,000 characters whatever you set: one real device/recall record carries 5,255,633 characters of lot numbers in a single code_info string, and Apify's per-item ceiling is 9 MB.

What do Class I, II and III mean on a recall? See the section above — they are the FDA's hazard classification of the recalled product, not a judgement about the company.

Why does drug/drugsfda have no date? Because the endpoint has no top-level date field. The only dates live inside submissions[], so recordDate is the latest submission_status_date and recordDateField says so. That array is not in date order, so "latest" is a sort, not submissions[0].

When is an empty result a failure? Never, by itself. A query that matched nothing gets a free no-results diagnostic and no charge. A run whose every query came back empty finishes FAILED — so a scheduled job tells you it found nothing rather than reporting success over an empty dataset — and still bills nothing.

Use it from an AI agent, or from code

One JSON input, one flat JSON array out — the shape agent runtimes handle best. No browser, no proxy requirement, no credentials of any kind. The Actor is configured for x402 agentic payments: pay-per-event pricing, event-only charging, limited permissions, no Standby mode. It is callable by name over the Apify MCP server, and the Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~clinical-trials-fda-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"interventions":["semaglutide"],"phases":["PHASE3"],"maxTrials":50,"fdaEndpoints":["drug/label","drug/enforcement"],"drugNames":["semaglutide"],"maxFdaRecords":50}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/clinical-trials-fda-api").call(run_input={
"conditions": ["type 2 diabetes"],
"statuses": ["RECRUITING"],
"maxTrials": 100,
"fdaEndpoints": ["drug/event"],
"drugNames": ["semaglutide"],
"fdaCount": ["patient.reaction.reactionmeddrapt.exact"],
"maxFdaRecords": 0,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["rowType"] == "trial":
print(row["nctId"], row["status"], row["leadSponsor"])
elif row["rowType"] == "aggregate":
print(row["aggregateRank"], row["aggregateTerm"], row["aggregateCount"])

Agent patterns that work well: answer "what is being tried for X" with conditions plus statuses: ["RECRUITING"] and a small maxTrials, citing sourceUrl per row; check a drug's regulatory footprint with drugNames across drug/label, drug/drugsfda and drug/enforcement in one call; get a reaction profile for free with fdaCount and maxFdaRecords: 0; resolve a specific study with nctIds before reasoning about it; and always read endpointLastUpdated before saying how current an adverse-event answer is.

Not affiliated with, endorsed by or sponsored by the U.S. Food and Drug Administration, the National Institutes of Health or the National Library of Medicine. "openFDA", "FDA", "ClinicalTrials.gov", and every brand, device and company name in the data are used descriptively to identify the records those organisations publish.

Both sources are US federal public-domain records published explicitly for programmatic access: ClinicalTrials.gov API v2 (https://clinicaltrials.gov/data-api/api) and openFDA (https://open.fda.gov/apis/). No login is used, no paywall is bypassed, no personal account is involved, and no data is collected beyond what those APIs return. openFDA's disclaimer and terms travel with every record in the raw column.

Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

Video, audio & social

News, documents & the web

Business, finance & jobs

Apps & games