pdufa.bio
Home › Developers

The PDUFA API

Page updated ·next FDA decision on the calendar ·Data as of Oct 11, 2026 9:31 PM ET
Every FDA catalyst, PDUFA dates, phase readouts, conferences, AdComm meetings: as clean JSON. Keyless and free for the core calendar. Run-up and cohort intelligence unlocks with Pro.
Base URL https://www.pdufa.bio/api/v1  ·  No key needed to start  ·  Tiers · Fields · Recipes · Errors
curl https://www.pdufa.bio/api/v1/pdufa?limit=3
Pro is not open yet. The free and anonymous tiers below work today and always will. Pro (run-up series, bulk export, .ics, webhooks) opens when the billing flow is finished end-to-end, get notified.

Tiers

TierPriceQuotaBurstFields
Anonymous
no key
$01,000/day10/minCore
Free
x-api-key
$010,000/mo30/minCore
Pro$10/mo · $100/yr100,000/mo120/minCore + Depth
Quant$149/mo2,000,000/mo600/minCore + Depth + History
Anonymous access stays free forever: every record links back to pdufa.bio. Attribution + link-back required on the free tiers.

How dates work, and one breaking change on 2026-09-09

date is an announced day, or it is null.
Until 2026-09-09 this field carried a month midpoint whenever date_precision was not day: 263 rows served the 15th and 62 more served the 30th or 31st, days no sponsor ever announced. Roughly seven rows in ten. Our own source comment said not to read those as days, which is no protection for a parser or for a language model summarising the feed. So the field now means one thing.
  • date_precision: "day", date is that day, sourced to an FDA notice, an SEC filing or the sponsor’s own release.
  • date_precision: "month", "quarter" or "year", date is null, and the granularity we actually hold is in date_month (for example "2026-06"). Most of these are ClinicalTrials.gov primary-completion estimates, and they move.
  • days_to_decision is null whenever date is. Counting down to a day we invented would be the same fabrication one field over.
If you sorted on date, sort on date_month first, then date. Rows with a real day still sort correctly inside their month, and filtering for date_precision === "day" gives you the dated calendar with nothing estimated in it.

What’s free, and what isn’t

Our rule: if it’s visible on a public page, it’s free in the API.
We previously gated fields, indication, NCT ID, market cap, cash runway, cohort statistics: that we publish in plain HTML on every /pdufa/* page. Charging for data anyone could scrape off the page was leaky and dishonest, so we stopped. All of it is now free.
Free: every request, no key needed
id · ticker · company · date · date_precision · name · type · therapeutic_area · market_cap_tier · status · url · updated_at · indication · nct_id · market_cap_usd · cash_runway_months · days_to_decision · cohort_move_median_pct · cohort_move_p25_pct · cohort_move_p75_pct · cohort_n · source · source_url · date_history · decision_source · decision_source_url · decision_date_note · decision_date_unsourced · goal_unsourced
Provenance fields (added 2026-09-15). source names the document a row rests on ("Corcept 8-K 2026-07-29 (EX-99.1)", "trial-estimate (not company-confirmed)") and source_url links it: an SEC filing, a ClinicalTrials.gov record, an FDA notice or the sponsor’s release. Both are null when we could not settle the row in a primary document, and a date_precision: "day" row with a null source_url is one we are still working to source. date_history is an array of every date the row has held and why it moved: [{"date":"2026-08-22"}, {"date":"2026-11-22","changed":"2026-08-24","why":"The FDA extended the goal date..."}]. Row ids embed the date a row was created on and are stable: when a goal date is extended or a registry estimate slides, the id keeps its original date and date_history records the move, so your stored rows stay one row. Ids are re-keyed only when the original date was our error, never for a legitimate change.
Pro: what we don’t publish as data
• The per-event run-up series: the full daily T-120 → T+5 price path for 1,791 FDA decisions, as numbers. We show a picture of it; we’ve never published the series. /api/v1/runup
• Bulk export of the whole dataset (CSV / JSONL)
• Subscribable .ics calendar feeds
• Date-slip webhooks
• 100,000 requests/month instead of 1,000/day

In other words: you pay for scale and for the series, not for secrets. The catalyst facts are free forever: that’s the whole point of the calendar.

Getting a key

You don’t need one to start. Anonymous requests work immediately: 1,000/day, no signup, no key. Just curl it.
When you want Pro: subscribe → your key is shown once on the success page → it also lives in your account, where you can copy it, regenerate it, watch your usage, and cancel.

Send it as a header: x-api-key: pk_live_… (or ?key= for quick tests).

Rate limits: published, so they feel fair

TierQuotaBurstCost per call
Anonymous1,000 / day10 / min1
Free (keyed)10,000 / mo30 / min1
Pro100,000 / mo120 / min1 · runup 5 · export 50
Quant2,000,000 / mo600 / minsame
Out of quota mid-job? You get a warning at 80%, a grace overage past 100% so nothing breaks, and only then a 402: with top-up credits that never expire. Burst abuse returns 429; a commercial limit is never disguised as an abuse error.

Endpoints

PathTierNotes
/api/v1/eventsAnyAll catalyst types
/api/v1/pdufaAnyFDA decision dates
/api/v1/readoutsAnyTrial readouts
/api/v1/conferencesAnyConference presentations
/api/v1/adcommAnyAdvisory committee meetings
/api/v1/usageAnyYour quota, credits, tier
/api/v1/healthAnyLiveness
/api/v1/runupProPer-event daily T-120→T+5 series  (5 credits)
/api/v1/exportProBulk CSV / JSONL  (50 credits)
/api/v1/calendar.icsProSubscribable calendar feed

Parameters

ticker (comma-separated) · type · ta · status · from · to · q (free-text) · limit (max 1000) · offset · include · format (json|csv) · id

Copy-paste recipes

Google Sheets: live PDUFA calendar in a cell
=IMPORTDATA("https://www.pdufa.bio/api/v1/pdufa?format=csv&limit=100")
Python / pandas
import pandas as pd
df = pd.read_csv("https://www.pdufa.bio/api/v1/pdufa?format=csv&limit=1000")
print(df[["ticker","date","name","market_cap_tier"]].head())
JavaScript
const r = await fetch("https://www.pdufa.bio/api/v1/events?type=PDUFA&from=2026-07-01");
const { data } = await r.json();
data.forEach(e => console.log(e.date, e.ticker, e.name));
Subscribe in your calendar (Pro)
https://www.pdufa.bio/api/v1/calendar.ics?key=YOUR_KEY&ticker=CELC,MNKD

Rate-limit headers

Every response tells you exactly where you stand, no surprises.

X-Api-Tier: anonymous
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1785542400
X-Credits-Remaining: 0
X-Credits-Cost: 1
X-Quota-State: ok          # ok | warning | grace | exhausted | unmetered
X-Request-Id: req_01J9Z...

We never hard-slam you. At 80% you get warning. Over 100% you get a grace overage so nothing breaks mid-job. Only past that do you get a 402, with credit and upgrade links, and still-cached data so your client degrades instead of dying. Burst abuse returns 429; a commercial limit is never disguised as an abuse error.

Errors

CodeMeaning
400 invalid_paramUnknown parameter: the message lists the valid ones
401 invalid_keyKey missing or unrecognised
403 tier_forbiddenEndpoint/field above your tier: includes _upgrade
402 quota_exhaustedCommercial limit: credit + upgrade options in the body
429 rate_limitedBurst abuse: honour Retry-After
503 degradedServing stale cache: check meta.served_from
Every error carries an X-Request-Id. Quote it and we can trace it.

Attribution

Free and anonymous use requires a visible link back. Copy-paste:

Catalyst data from <a href="https://www.pdufa.bio">pdufa.bio</a>
Need Depth? Run-up series, cohort distributions, NCT IDs, bulk export and calendar feeds come with Pro, $10/mo or $100/yr. See pricing →

Questions

Is the pdufa.bio API free?

Yes. The read API at /api/v1/ is free, and /llms.txt documents it for AI agents.

What does the API return?

The tracked catalyst dataset: PDUFA dates, readout windows, conference presentations and advisory committee meetings, each record with its canonical page URL.

How current is the API?

Rebuilt daily with the site; the as_of field carries the build date.