NIH RePORTER Grants Scraper – Federal Research Funding API avatar

NIH RePORTER Grants Scraper – Federal Research Funding API

Pricing

$1.50 / 1,000 result items

Go to Apify Store
NIH RePORTER Grants Scraper – Federal Research Funding API

NIH RePORTER Grants Scraper – Federal Research Funding API

Search NIH grants via the official NIH RePORTER API: keyword, fiscal year, institute, activity code, org, state, PI, award-amount and award-date filters, plus an optional PubMed join. Auto-chunks past the 15k-row wall. Watch mode returns only what's new since last run. Science research grants API.

Pricing

$1.50 / 1,000 result items

Rating

0.0

(0)

Developer

Fetch Smith

Fetch Smith

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

NIH RePORTER Scraper – Federal Research Funding API, No 15k Row Cap, PubMed Join

Export every NIH-funded research project from the official NIH RePORTER API — 2.9M+ project records going back to 1985 — filtered by keyword, fiscal year, institute (IC), activity code, organization, state or principal investigator. Each project comes back with its award amounts, PI and program officer, institution, study section and congressional district, plus an optional join to the PubMed papers that project produced.

No API key, no login, no proxy. Public data only.

It is a grant data API for NIH RePORTER: pass a keyword, fiscal year or institute and get research grants data back as flat rows — award amount, PI, organization, administering institute, congressional district — with no web form, no pagination and no 15,000-row wall. NIH is the largest public funder of biomedical research in the world, so this is one of the broadest public sources of science funding data there is, and it is research funding data you can join directly to the PubMed papers each award produced. It behaves like a grant database API rather than a scraper: every field comes straight from NIH's own JSON.

What it's for

  • Biotech / pharma competitive intelligence — who is NIH-funded in your therapeutic area, at what dollar level, and which papers came out of it.
  • University research offices & grant consultants — benchmark awards by institute, activity code (R01 vs R21 vs SBIR R43/R44) or peer institution.
  • Research-tooling and CRO sales teams — find labs that just received a new (awardType: "1") award in your space, with the institution and city attached.
  • Bibliometrics / science-of-science — a funding-to-publication link for a whole field in one dataset.
  • COVID-19 research funding tracking — NIH RePORTER carries 58,561 projects matching COVID-19 in the title, terms or abstract (verified live via keyword: "COVID-19"); pair it with fiscalYears to see funding shift year over year as the pandemic response evolved.

Three things this Actor does that the API doesn't

1. It gets past the 15,000-row wall. NIH RePORTER caps offset + limit at 15,000 and offers no cursor — a single fiscal year already holds 83,000+ projects, so a broad query is simply untraversable against the raw API. This Actor detects when a query exceeds the wall and automatically splits it across fiscal year, then administering institute, then award type, merging and de-duplicating on appl_id so you never pay twice for the same record.

2. It refuses to silently ignore your filter. NIH RePORTER accepts an unrecognised criteria field name with HTTP 200 and returns the entire unfiltered index as if the filter had worked — "success" that quietly means "everything". Every criteria key is validated against an allowlist before the request is sent, so a filter either applies or the run fails loudly. The same silent-empty-search trap exists one level down, on the value of activityCodes and orgStates: a well-formed but nonexistent code (e.g. a typo like R101, or UK/US instead of a real state) gets HTTP 200 with zero matching rows, indistinguishable from a genuinely empty search. This Actor checks every supplied activity code against a live-observed list of ~201 real codes (sampled directly from NIH RePORTER's own data — NIH publishes no reference endpoint for this vocabulary) and every supplied org state against the real 70-code closed set (50 states + DC + 5 territories + 3 Compact-of-Free-Association states + UM + 10 Canadian provinces, found by brute-forcing all 676 two-letter combinations against the live API), warning in the run's log if one isn't recognised — though it still sends the value as-is, in case it's a real, very-rare code our sample missed.

2b. One org state is coded twice, and the Actor tells you. Quebec appears in NIH RePORTER's data under both QC and the legacy postal abbreviation PQ, in two sets that are both still actively written today — measured live 2026-10-20: 244 projects under QC, 605 under PQ, zero appl_id overlap (a clean partition, not two views of one set). Both codes are real, so a validity check cannot catch this: orgStates: ["QC"] is a perfectly valid filter that returns a real, non-empty result while silently omitting 605 of Quebec's 849 awards — and the omission is not an even split. Every award ever coded QC belongs to one of just 2 organizations (McGill University FY2008-2025, Clinical Research Institute of Montreal FY1995-2016); the other 19 Quebec organizations — Université de Montréal, Laval, Concordia, UQAM, Montreal Heart Institute, Sainte-Justine, MUHC and the rest — have never had a single QC award and are still filing under PQ in FY2026 (FY2020+: 47 QC awards from 1 org vs 62 PQ awards from 8). PQ is therefore not a legacy code you can age out of; McGill is the only org appearing under both, split at a clean FY2008 cutover that no other institution shares. Ask for one without the other and the run logs a warning naming the missing code. For all of Quebec, pass orgStates: ["QC","PQ"]. Full measurement, per institution.

3. It remembers what it already sent you (watchLabel). Give a query a name — watchLabel: "crispr-nci-weekly" — and the Actor keeps a per-label record of the projects it has already delivered to you, so a scheduled run returns only what is new since last time and you are charged for nothing else. The first run on a new label is a free baseline run: it records what already matches, returns zero results, and charges nothing. Every run after that returns new projects only. This is your own baseline, not NIH's: newlyAddedOnly uses RePORTER's stateless "recently added to the index" flag, which keeps re-returning the same projects on every run until they age out of it. Change any other filter and the label starts a fresh baseline, so "new" always means new for the exact query you are watching.

  • watchChanges — also catch a no-cost extension, an administrative supplement, or a project ending. Add this to watchLabel and a project you already have gets re-delivered (at the normal per-row price, tagged _watchChangeType/_watchPrevious) if its project end date, budget end date, award amount or active flag changes since you last saw it — not just brand-new projects. NIH itself confirms a no-cost extension updates the same award record's end date rather than minting a new appl_id (unlike a competing/non-competing renewal, which always gets a fresh appl_id), so this is a real, detectable signal, not a guess. Off by default so existing watches keep their current behavior.

  • Never pin a watch to awardNoticeDateTo. It is an upper bound sent to NIH RePORTER as award_notice_date.to_date. Measured live: of the 500 most-recently-added projects in the index, a newly-awarded project's own award_notice_date is within a few months of being added (median 32 days, 0/333 past 365 days), so once the real calendar date passes a fixed cutoff by more than that, this watch stops reporting ordinary new awards while still logging a healthy "nothing new" every run. One measured exception keeps this from being absolute — a single backdated/corrected record (added 2026-08-22 but dated 2010-08-16) did turn up — but that is index-correction noise, not ongoing award activity, so it doesn't rescue the watch. A run in this state logs two warnings and sets watchDeadDateBound: true in RUN_SUMMARY/the webhookUrl payload. awardNoticeDateFrom alone is always safe to keep in a watch.

Skip the form: paste your RePORTER search URL (startUrl)

Build the search on reporter.nih.gov the way you normally would, then paste the address bar into startUrl:

https://reporter.nih.gov/search/FIJedD1bG0epAlP7QhG9lw/projects

That search_id in the path is a handle NIH stores on its own side, and NIH's public API accepts it in place of a criteria object — so the Actor runs exactly the search you built in the browser, including filters this Actor's own inputs don't expose. Any tab of a shared search works (/projects, /charts, /publications, …), and a bare search id on its own works too.

Two things to know:

  • A saved search replaces the filter fields, it does not combine with them. NIH discards any criteria sent alongside a search id (silently — this Actor does not pass them and the run log names any you left filled in). maxResults, includeAbstract, includePublications and watchLabel still apply normally, and a watchLabel baseline is keyed to the specific search id, so two different pasted links never share one baseline.
  • NIH doesn't promise a shared link lives forever. If a link stops resolving, the run fails with a message that says so instead of returning an empty dataset. Re-run the search on reporter.nih.gov and paste the fresh link.

The 15,000-row wall still applies to a pasted search and cannot be worked around: the automatic chunking below rewrites the query's filters, and a saved search doesn't expose any to rewrite. If your link matches more than that, the log says so — narrow it on reporter.nih.gov, or use the filter fields instead.

Input

FieldTypeDescription
startUrlstringPaste a reporter.nih.gov search link instead of re-typing its filters — see below. Replaces every filter field in this table
keywordstringFull-text over project title, abstract and NIH terms. Omit it (or leave it empty) to search on the other filters alone — e.g. every FY2025 project at one organization. Only a run with no filters at all falls back to the example keyword "cancer"
fiscalYearsarraye.g. [2024, 2025]. Empty = all years
agencyIcCodesarrayAdministering institute, e.g. ["NCI","NIAID"]
activityCodesarrayAward mechanism, e.g. ["R01","R21","R43"]
awardTypesarray1 new, 2 renewal, 3 supplement, 5 continuation, …
orgNamesarrayFunded organization, partial match
orgStatesarrayTwo-letter org state code, e.g. ["CA","MA","NY"]. Also DC, 5 US territories, 3 COFA states, UM, and 10 Canadian provinces. For all of Quebec use both ["QC","PQ"] -- see FAQ
piNamesarrayPI name contains, e.g. ["Doudna"]
minAwardAmountintegerOnly awards of at least this many dollars (e.g. 1000000)
maxAwardAmountintegerOnly awards of at most this many dollars — combine with the minimum for an exact band
awardNoticeDateFromstringOnly projects officially awarded on/after this date, YYYY-MM-DD — see FAQ
awardNoticeDateTostringOnly projects officially awarded on/before this date, YYYY-MM-DD — combine for an exact window
projectNumsarrayExact lookup — ignores every other filter
activeOnlybooleanCurrently active projects only
newlyAddedOnlybooleanOnly projects recently added to RePORTER — cheap incremental pulls
watchLabelstringName a saved query to get only projects new since its last run — see below
watchChangesbooleanOptional, requires watchLabel. Also re-deliver an already-seen project if its end date, budget end, award amount or active flag changed (default false) — see FAQ
webhookUrlstringOptional. POST a small JSON completion summary (projects pushed, watch new/changed counts, dataset ID) here when the run finishes — see FAQ.
excludeSubprojectsbooleanDrop P01/U54 subproject duplicates (default true)
includePublicationsbooleanJoin linked PubMed PMIDs (default true)
includeAbstractbooleanInclude abstract + public-health relevance (default true)
maxResultsintegerStop after this many records (default 100)

projectNums is an exclusive mode on purpose: looking up R01CA234538 while a fiscal-year or institute filter is set would otherwise return zero rows, which is indistinguishable from a typo. Ask by number, get that project.

Output

applId, projectNum, coreProjectNum, subprojectId, fiscalYear, projectTitle, activityCode, awardType, fundingMechanism, awardAmount, directCostAmt, indirectCostAmt, isActive, isNew, projectStartDate, projectEndDate, budgetStart, budgetEnd, awardNoticeDate, dateAdded, agencyCode, icCode, icAbbreviation, icName, icFundings[], opportunityNumber, cfdaCode, studySection, spendingCategoriesDesc, contactPiName, principalInvestigators[], programOfficers[], orgName, orgCity, orgState, orgCountry, orgZip, orgDeptType, orgUei, orgType, congDist, latitude, longitude, terms[], url, abstractText, publicHealthRelevance, publicationCount, pubmedIds[]

Sample row (trimmed):

{
"projectNum": "5R01CA279801-02",
"coreProjectNum": "R01CA279801",
"fiscalYear": 2024,
"projectTitle": "Orthogonal CRISPR GEMMs",
"activityCode": "R01",
"awardAmount": 635830,
"directCostAmt": 393703,
"icAbbreviation": "NCI",
"studySection": "Genomics, Computational Biology and Technology Study Section[GCAT]",
"contactPiName": "MCMANUS, MICHAEL T",
"orgName": "UNIVERSITY OF CALIFORNIA, SAN FRANCISCO",
"orgState": "CA",
"congDist": "CA-11",
"publicationCount": 0,
"pubmedIds": [],
"url": "https://reporter.nih.gov/project-details/10794392"
}

Field population — read this before you build on a field

Grant records (R/P/U/K/F activity codes) populate almost everything. R&D contract records (N01, funding_mechanism: "R and D Contracts") are much thinner — verified live: publicHealthRelevance, awardType, opportunityNumber, cfdaCode and studySection all come back null, and directCostAmt/indirectCostAmt are usually absent on older rows too. Filter on fundingMechanism if you need a uniformly populated set.

subprojectId is empty on most rows by design — it only exists on multi-component awards. Measured live on a 500-project FY2025 sample (2026-10-12): filled on 82/500 (16.4%), and every single one was a multi-project center or program mechanism — P30 (28), U54 (19), P01 (12), P50 (12), U19 (4), P20 (2), U2C (2), UM2 (1). NIH only mints a subproject id when one parent award is split into separately-tracked components (a center grant's individual cores and projects, e.g. 5P01AG052350-08 subproject 9078); a single-project award like an R01 has nothing to subdivide, so it has no subproject id to report. A null here is therefore a fact about the award's structure, not a missing value — use it to tell center components apart from standalone awards, and group on coreProjectNum if you want the parent award rather than its parts.

publicationCount is legitimately 0 for many recent projects — papers take years to appear, so a 2024 new award usually has none yet. It is not a join failure. publicationCount: null means something different and is never mixed up with 0: the PubMed lookup for that project didn't get an answer from NIH, so the count is unknown, not zero. The run log names every project it happened to and RUN_SUMMARY.publicationLookupBatchesFailed counts them.

FAQ

Does it need an NIH account or API key? No. NIH RePORTER's API is fully public.

I got 0 rows and the run shows "Succeeded" with no status message — is that a bug? It was, and is now fixed: a run that genuinely matches nothing still succeeds (the search itself worked), and the Apify console's run-list status now names the actual reason inline — "no project matched your filters", "nothing new for this watch label", "no project found for that project number", or "that saved search resolved to zero projects" — instead of leaving the default "Succeeded" label with no explanation. The full checklist of common causes (narrow ANDed filters, an unrecognised IC/activity/state code, pre-1985 fiscal years) stays in the run log; the console message is a short pointer to it. A pathologically long watchLabel or projectNums list is word-wrapped to fit Apify's 500-character status-message cap rather than silently cut mid-word.

I got 0 rows and incompleteReason says count-request-failed or search-request-failed — did my filters just not match anything? No — those two reasons mean NIH RePORTER's own API never answered the request, so how many projects match is genuinely unknown, not zero (a true empty result reports complete: true instead). The most common live cause is NIH's eRA backend itself going down: api.reporter.nih.gov answers with a redirect to its own fallback.era.nih.gov/systemUnavailable outage page, which this Actor detects by name and reports as "NIH's eRA system is reporting System Unavailable" rather than a raw HTML error dump. This is an outage on NIH's side (confirmed to persist for hours, not seconds, during the 2026-10-18 incident) — re-run later rather than narrowing your filters.

How do I get more than 15,000 rows? Just raise maxResults; the chunking is automatic. If a query is so broad that even chunking can't reach the rest, the log says so explicitly rather than silently truncating.

Why does one project appear several times? NIH RePORTER records one row per fiscal year of funding. R01CA234538 returns six rows for its six funded years. De-duplicate on coreProjectNum if you want one row per project.

Can I filter by award size? Yes — minAwardAmount and maxAwardAmount, either alone or as a band. One caveat we measured rather than assumed: NIH RePORTER excludes projects with no award amount recorded from any amount-filtered query — about 2.8% of a 500-row FY2024 sample, matching a 2.6% drop in the reported total. So an amount filter is slightly narrower than "every project in that range"; the run log warns you whenever one is active.

Does activeOnly narrow down a fiscalYears filter, or replace it? It narrows it — this Actor handles it for you. We measured NIH RePORTER's own API live and found it unions activeOnly with fiscalYears server-side instead of intersecting them (on a narrow agency, ~6k FY2025-alone + ~7.6k active-alone combined into ~12.1k together — close to the sum, not a subset of either). Since build 0.1.4x, when you set both, this Actor no longer sends include_active_projects to NIH at all; instead it queries fiscalYears alone and filters isActive === true on the results itself, so you get the true intersection. The one side effect: the run may deliver fewer rows than declaredMatches reports (the same way minAwardAmount/maxAwardAmount above can), because declaredMatches describes NIH's count for the fiscalYears-only query, and the isActive filter narrows further on top of that. newlyAddedOnly never had this quirk; it correctly intersects with fiscalYears on NIH's side already.

Can I filter by the actual award date instead of fiscal year? Yes — awardNoticeDateFrom/awardNoticeDateTo, e.g. "grants awarded in June 2024". This is a different axis from fiscalYears: a project's NIH fiscal year rarely lines up with its calendar award date (a project awarded late in FY2024 might carry a notice date in mid-2024 but effective dates that read like FY2025). Both fields must be YYYY-MM-DD — NIH RePORTER's API silently ignores any other date format rather than rejecting it, so this Actor validates the format itself and fails the run loudly instead of quietly returning an unfiltered result set.

My watch says "nothing new" every single run — is it broken? Check whether you set awardNoticeDateTo. A watch combined with a fixed upper date bound is close to a dead end: a newly-awarded project's award_notice_date is almost always within a few months of being added to the index (measured live — 0 of 333 recently-added, dated projects sampled had a gap past 365 days), so once the real calendar date passes your cutoff by more than that, no ordinary new award can satisfy it anymore, and the run honestly reports zero new rows run after run. (One measured exception: a single backdated/corrected record did turn up outside that window — index-correction noise, not ongoing award activity, so it doesn't make the bound safe to keep.) Such a run now logs two warnings and sets watchDeadDateBound: true in RUN_SUMMARY/the webhookUrl payload. Fix it by dropping awardNoticeDateTo from the watched query — keep awardNoticeDateFrom if you want a floor — or drop watchLabel if you genuinely wanted a one-off historical window rather than an ongoing watch.

How do I pull only what's new since my last run? Two options. watchLabel is the precise one: name your query, and the Actor returns only the projects it has not already delivered under that name (first run = free baseline, zero results). newlyAddedOnly: true is the coarse one — RePORTER's own "recently added to the index" flag, about 8.9k projects index-wide when measured, almost all current-fiscal-year. Use watchLabel for a scheduled alert on a specific query; use newlyAddedOnly for a cheap sweep of whatever NIH just published. They combine fine.

What does watchChanges add, and does it cost extra to turn on? No extra fee — a changed project is billed at the same per-row price as a new one, so you only pay when there is actually something to see. Plain watchLabel only ever tells you about projects it has never delivered before; it stays silent forever about one it already sent you, even if NIH later grants a no-cost extension, processes an administrative supplement, or the project ends. Set watchChanges: true and each run also compares every already-delivered project's end date, budget end, award amount and active flag against what they looked like last time; if any moved, the row is re-delivered tagged with _watchChangeType (which field(s) changed) and _watchPrevious (what they used to be). This is a real signal, not a guess: NIH's own eRA Commons documentation confirms a no-cost extension updates the same award record's project period end date rather than creating a new appl_id — unlike a competing or non-competing renewal, which always gets a fresh appl_id per fiscal year. Verified live: seeding a baseline, editing 2 projects' recorded end date and active flag directly, then rerunning returned exactly those 2 rows with the correct change tags and nothing else — and a plain unchanged rerun after that returned 0 rows again. Existing watch labels created before this feature shipped work immediately; the first run under watchChanges just starts detecting drift from that point forward rather than reporting an artificial backlog.

I set watchChanges AND minAwardAmount/maxAwardAmount/activeOnly — why does it never report a change? Don't combine them with the field you care about. minAwardAmount/maxAwardAmount are sent to NIH RePORTER as its own server-side award_amount_range filter on the exact award-amount field watchChanges watches — live-measured: a $957,843 award is returned with maxAwardAmount=2000000 and excluded with maxAwardAmount=900000. The instant a watched award's amount moves out of your bound (a no-cost extension or supplement raising it, a de-obligation lowering it), the row stops matching the query entirely, so the change is never compared and never reported. activeOnly does the same thing to the active flag, whether it's applied as NIH's server-side include_active_projects filter or (when combined with fiscalYears) this Actor's own client-side intersection — live-measured: an inactive project is excluded either way. The moment a tracked project's active period ends, it stops matching an activeOnly query, so the active → inactive transition — the one alert this flag mainly exists to catch — is the one it cannot deliver. The run logs an explicit warning plus a NOTE on every "nothing new" status line whenever this applies; it's advisory only, since all three inputs default to off. Run the watch label without the overlapping filter if you need that specific change reported.

Where is the watch baseline kept, and can I reset it? In a named key-value store, fetchsmith-nih-watch, on your own Apify account — one record per label + filter combination, holding the appl_ids already sent plus the last run time. Delete the record (or just use a new label) to start over. Nothing about your saved queries leaves your account. Two caveats worth knowing: a project that was skipped because it fell outside maxResults is not marked as delivered, so it comes back on the next run; and a baseline caps at 15,000 projects (RePORTER's own paging wall), so watch a query narrow enough to fit under that — the run log warns you if it doesn't.

Baseline size cap. Separately from the 15,000-project seeding wall above, the saved record itself holds up to 60,000 appl_ids for one label. If a long-running label's baseline grows past that, the oldest ids are dropped — and a dropped id is no longer recognised, so it comes back as "new" on a later run and is charged again. The run that drops any says so explicitly: a warning in the log, a note appended to the seeding/incremental log line, a status message on an otherwise-complete run, and baselineTruncated/baselineTruncatedTotal (this run / the whole life of the label) in the saved record and in RUN_SUMMARY. If you see it, narrow the query (a fiscal year, IC or state) or split it across several labels so each baseline stays under the cap.

Is any personal contact data collected? No. The NIH RePORTER schema contains no email or phone field at all. PI and program-officer names are included because they are statutory public disclosure, published on every reporter.nih.gov project page — the same class of data as a federal contract awardee's name.

How do I know the run returned everything it should have — in code, not by reading the log? Every run writes a RUN_SUMMARY record to its key-value store, readable with no webhook set up:

GET https://api.apify.com/v2/actor-runs/<runId>/key-value-store/records/RUN_SUMMARY?token=<token>
{
"mode": "search",
"declaredMatches": 19126,
"reachableMatches": 19126,
"unreachableMatches": 0,
"scanned": 5,
"delivered": 5,
"pages": 1,
"complete": false,
"incompleteReason": "max-results",
"incompleteDetail": "maxResults=5",
"maxResults": 5,
"chunksPlanned": 39,
"chunksScanned": 1,
"chunksEmpty": 0,
"chunksCountFailed": 0,
"publicationLookupBatchesFailed": 0,
"watchLabel": null,
"baselineSize": null,
"baselineTruncated": null,
"baselineTruncatedTotal": null
}

declaredMatches is NIH RePORTER's own meta.total for your filters, so delivered vs declaredMatches is the shortfall, computed against NIH rather than against our own paging. complete: false is not a failure — a run capped by maxResults is short on purpose — it means don't treat this dataset as the whole answer. incompleteReason is one of max-results, charge-limit, seed-cap, offset-wall (matches past NIH's hard 15,000-row paging wall), count-request-failed, search-request-failed, empty-page-before-total, not-reached. A short run also sets the Apify status message, so the shortfall is visible in the Console without opening the log.

Important: a number is never invented from a failure. If NIH doesn't answer a count query, declaredMatches is null — never 0 — and if it stops answering mid-walk the run says search-request-failed instead of reporting a partial page set as the complete result. The same rule applies to a chunked query: a sub-query whose count fails is paged anyway and counted in chunksCountFailed, so a single failed request can't quietly delete an entire NIH institute from your results.

I run this on a schedule as a watch — what should my pipeline check? complete. A watch baseline (mode: "watch-seed") that was cut short records fewer already-seen projects than really match, and every project it missed looks brand new — and gets charged — on the next incremental run. When that happens the run logs a BASELINE INCOMPLETE warning, sets complete: false, and stores lastRunComplete: false in the watch record, so re-seed before trusting the next run. On a healthy seed, baselineSize equals declaredMatches. Also check baselineTruncated/baselineTruncatedTotal (watch mode only) — a non-zero value means the 60,000-id record cap dropped older ids this run, and those will be re-delivered and charged again next run; see the "Baseline size cap" FAQ above.

How is webhookUrl different from Apify's own platform webhooks? Apify's platform webhooks are configured separately per Task/Actor via the Console or the Webhooks API — useful if you already live in the Apify Console, but extra setup if you're calling this Actor's API directly and just want a completion ping. webhookUrl is a plain input field: set it on the run itself and it POSTs a JSON body (actorRunId, defaultDatasetId, finishedAt, pushed, the full summary object described above, and — if watchLabel is set — watchSeeding/watchNewCount/watchChangedCount) once the run finishes and every row is already pushed and charged. It's best-effort — a slow or failing webhook only logs a warning, it never fails the run, changes the result set, or affects billing.

Pricing

result — $0.0015 per returned item, no Actor-start fee. There is nothing to pay before the first row arrives, and the price is flat: it does not depend on which Apify plan you are on.

Where that sits in the niche (all prices read from each listing's live Apify pricing record, verified 2026-10-06). A seven-term Store sweep finds that 51 Store listings mention NIH or RePORTER in their name, title or description (a deeper paginated sweep of the same terms reaches 53, our own two listings included), and we priced every one of them live. Most are more expensive than this Actor per row: nexgendata/us-grants-funding-tracker (61 users — the niche's largest listing by users) and nexgendata/nih-reporter-grants-scraper both charge $0.05 per award record, over 30× our price; red.cars/nih-grants-mcp charges $0.05 per grant search; nexgenwatch/grant-funding-report charges $15 per report plus a $0.05 start fee, and nexgenwatch/nih-reporter-grant-award-delta charges a mandatory $0.1 (Free) down to $0.067 (Gold and above) source-check fee on every run, on top of $0.15 (Free) down to $0.1005 (Gold and above) per award delta and a $0.02 start fee; taroyamada/nih-grant-publication-output-report ($0.008/row) and taroyamada/nih-research-funding-landscape-report ($0.005/row) both also sell $8–$15 report and export events; scrapers_lat/usa-nih-reporter-scraper is tiered $0.012 (Free) down to $0.0102 (Gold and above) per row, and sells four optional AI enrichment events on top (summary, keywords and field classification at a flat $0.012 each, structured grant-profile extraction at $0.015, none tiered); parseforge/nih-reporter-scraper is tiered $0.0065 (Free) down to $0.006 (Gold and above); dataio/nih-reporter-funded-investigators is $0.006/row and devilscrapes/nih-reporter-grants-scraper $0.006/row plus a $0.20 start fee; crawlerbros sells two listings, crawlerbros/nih-reporter-scraper and crawlerbros/nih-reporter-grants-scraper, both $0.005/row on the free plan tapering to $0.003 on Gold and above, each plus a $0.005 start fee; scrapemint/nih-grant-finder, foo121/nih-reporter-scraper and thirdwatch/nih-reporter-projects-scraper are all $0.004/row on the free plan; fortuitous_pirate/grants-gov-scraper (never named before, checked live 2026-10-04 — a Grants.gov scraper that reaches this sweep only because its description lists NIH among the agencies you can filter by, so it returns open funding opportunities rather than awarded RePORTER projects) is $0.004375/row plus a $0.001 start fee; tagadanar/us-grants-monitor charges three separate per-row events, each tiered, plus a flat $0.001 start fee: $0.003 (Free) down to $0.0021 (Gold and above) per NIH award history record — the event comparable to ours, dearer than our $0.0015 at every tier — alongside $0.004 (Free) down to $0.0028 (Gold and above) per Grants.gov opportunity returned, which is its flagged primary event, and $0.03 (Free) down to $0.021 (Gold and above) per webhook digest posted in monitor mode; andrew_avina/nih-reporter-mcp, foxlabs/nih-reporter-organization-data and knotty_mistveil/nih-reporter-projects are $0.003/row; benthepythondev/nih-funded-projects-scraper is $0.0025/row on the free plan ($0.00175 on Diamond); chrisp1211/nih-grants-scraper-max and pink_comic/nih-reporter-search are both $0.002/row, the latter plus a $0.0001 start fee; fortuitous_pirate/nih-reporter-scraper and quarterly_jingo/nih-reporter-scraper are $0.00186/row plus a $0.001 start fee; maximedupre/nih-reporter is $0.0018/row and parseforge/nih-reporter-publications-scraper is tiered $0.0018 (Free) down to $0.00163 (Diamond); and caffein.dev/grants-actor (never named before — reads NIH RePORTER plus Grants.gov and Duke Research Funding in one unified dataset) charges $0.002 per NIH/Grants.gov result plus a $0.00005 start fee. Four more listings surfaced in a fresh sweep (2026-10-05) and are also never named before: scrapesage/us-federal-grants-scraper (open Grants.gov opportunities plus awarded NIH/NSF projects with contact enrichment) charges $0.005 per funding record plus a $0.01 per-organization contact-enrichment event, over 3× our rate before enrichment is even added; automation_studio/federal-grants-sbir-funding-radar (Grants.gov, SBIR/STTR, NIH, NSF, DoD, DOE and NASA) is tiered $0.005 (Free) down to $0.003 (Gold and above) plus a $0.001 Actor-start fee, 2–3.3× our rate at every tier; and two apparent clone listings sharing the identical title "NIH Nsf Funding Scraper — No Login, $4.38/1k", fortuitous_pirate/nih-nsf-funding-scraper and quarterly_jingo/nih-nsf-funding-scraper, both charge $0.004375/row plus a $0.001 start fee, roughly 2.9× our rate — the same fortuitous_pirate/quarterly_jingo clone-pair pattern already seen elsewhere in this niche (see fortuitous_pirate/nih-reporter-scraper and quarterly_jingo/nih-reporter-scraper above). A further eleven listings were priced live on 2026-10-05 and had never been named here, which closes the gap between the 51 the sweep matches and the handles this section lists. Four read the same NIH RePORTER data we do and all cost more per row: alaudinburki/funded-lab-radar (RePORTER v2 by topic, ranked by award size and remaining runway for sales use) charges $0.004/row plus a $0.0001 start fee, about 2.7× our rate; mambalabs/public-award-monitor (up from 2 — its us_nih_sbir register reads NIH RePORTER, per its own input schema, but returns one row per winning company rather than one per project) is tiered $0.005 (Free), $0.00475 (Bronze), $0.0045 (Silver) and $0.00425 (Gold and above) plus a $0.00005 start fee, 2.8–3.3× our rate at every tier; and datasignalslab sells two more listings beyond the already-named datasignalslab/nih-research-funding-monitor that both carry NIH RePORTER grants as one of twelve filing streams — datasignalslab/filing-events-api at a flat $0.02 per filing event, about 13× our rate, and datasignalslab/datasignals-lab-events-mcp, which carries no Apify pricing record at all and so costs nothing per row on Apify. By the rule we apply everywhere else that would make it the cheapest listing in this sweep, but we are not presenting it as one: its own README says five of its seven tools — including search_events, the one you would actually query grants with — require a DATASIGNALS_KEY issued by the vendor, so the Apify-side $0 is not the price of using it. Four more are dearer and read a different NIH dataset: crawlerbros/icite-scraper (a third crawlerbros listing beyond the two named above; NIH iCite PubMed publications and citation metrics, not grant awards) is $0.005/row on the free plan down to $0.003 on Gold and above, plus a $0.005 start fee; parseforge/pharos-target-druggability-scraper (NIH Pharos target druggability by gene symbol) is tiered $0.0085 (Free) down to $0.00769 (Gold and above) with optional $0.005-and-down ligand and disease add-on events, roughly 5× our rate; and parselab/nih-clinical-trials-scraper ($0.001 start plus tiered $0.025 down to $0.01875 per study) and antishock/clinicaltrials-gov-studies-scraper ($0.002 start plus $0.002 per result, with a further pricing entry scheduled for 2026-10-16 at the same two rates) both scrape ClinicalTrials.gov studies rather than grant awards, and reach this sweep only because ClinicalTrials.gov is an NIH service. One is an exact price tie: andrew_avina/grants-mcp charges $0.0015 per result item, the same as us, for Grants.gov open opportunities rather than awarded RePORTER projects — the same class as fortuitous_pirate/grants-gov-scraper above. Two listings inside this 51-listing sweep do charge less per row than our $0.0015, and neither returns NIH RePORTER data: andrew_avina/sbir-intelligence-mcp charges $0.0005/row plus a $0.00005 start fee, a third of our rate at every tier, but its Store description says SBIR.gov while its own live README and badge say the awards come from USASpending.gov, and its input schema exposes SBIR/STTR award fields (agency, phase, amount) with no RePORTER project numbers, principal investigators, study sections or PubMed join; and copious_atoll/clinical-trials-scraper charges $0.0005/row plus a $0.00005 start fee for ClinicalTrials.gov studies, a dataset this Actor does not cover at all (it is already named in our own clinicaltrials-scraper listing's comparison). Among the listings that actually return NIH RePORTER grant and project records, we remain the cheapest per row. We do not claim to be the cheapest in this sweep overall, let alone in the niche: the two listings just named are cheaper per row for their own datasets, and seven more beat us on RePORTER data itself in real scenarios — those seven are named below.

What we do not claim. We are not the cheapest option for every job. Seven rival listings genuinely beat us in specific situations, each one's pricing record read live and verified 2026-10-06. One is simply free: constant_quadruped/research-grant-aggregator (the second-largest listing in this sweep by users) carries no Apify pricing record at all, which means it runs on Apify's free pricing model and charges nothing per row beyond the platform compute your own account uses; it searches NIH, NSF, Grants.gov and USASpending in one query, so if price is your only criterion, start there. Two undercut our flat rate on ordinary runs: themineworks/nih-reporter-grants charges $0.001 per grant on Free, $0.0009 on Bronze, $0.00075 on Silver and $0.0006 on Gold and above, plus a $0.005 start fee — cheaper than us past roughly 10 rows per run on Free, 9 on Bronze, 7 on Silver and 6 on Gold and above; and publicmoney/nih-reporter-grants-scraper prices its Grant event per Apify plan — $0.002 on Free, $0.0015 on Bronze (a tie with us), then $0.00125 on Silver, $0.001 on Gold, $0.00085 on Platinum and $0.0007 on Diamond — so on any paid Apify plan above Bronze it is cheaper per row than we are, though it adds a $0.00005 start fee. Four more trade a fixed per-run or per-scan fee for a near-zero row price, which wins on larger pulls: datasignalslab/nih-research-funding-monitor — previously misread by this README as a flat "$0.02/row" — actually charges that $0.02 per organization or topic scanned, not per row (its own event description says so), plus a separate $0.00001 per matched grant row and a $0.00005 start fee, which makes it cheaper than our flat $0.0015/row past roughly 14 grants returned per scan, the same headline-number trap documented elsewhere in this fleet; jungle_synthesizer/nih-reporter-grants-publications-scraper charges $0.10 per run plus $0.0005 per record, making it cheaper than us past roughly 100 rows; alizarin_refrigerator-owner/nih-grants-api-research-funding-data-for-grants-publications charges $0.10 per run plus $0.01 per search operation and only $0.00001 per row, making it cheaper than us past roughly 75 rows — about $0.12 against our $1.50 for a 1,000-row pull; and zentrafoundry/nih-reporter-competitor-grant-win-alert (repriced 2026-10-01) charges $0.01 per award scan plus $0.0001 per matched, saved or enriched award record, which on its published event prices works out cheaper than us past roughly 10 awards per run. If your workload is one big export and price per row is the only thing you care about, those are the honest alternatives. Two further listings found in the 2026-10-05 sweep charge less per row than we do — andrew_avina/sbir-intelligence-mcp at $0.0005 and copious_atoll/clinical-trials-scraper at $0.0005 — and are listed above rather than here because neither returns NIH RePORTER records at all (USASpending SBIR/STTR awards and ClinicalTrials.gov studies respectively); if those datasets are what you actually want, they are cheaper than us and we are not a substitute for them either. We are the better pick for small pulls and for repeated incremental runs where a per-run fixed fee is charged over and over (ours is $0.0015 flat with no start fee, so a 5-row watch run costs $0.0075 and nothing else), for anyone on Apify's Free or Bronze plan, and for the filtering, validation and change-detection described next — which is the real reason to pick this Actor over a cheaper row price.

Beyond price, this Actor adds award-amount/award-date range filters, an org-state filter, activity-code validation (with an unrecognised-code warning against a live-sampled 201-code list), an optional PubMed publication join, auto-chunking past the API's 15,000-row offset wall, and watch-mode change detection (new projects plus no-cost extensions/budget/status changes on ones you've already seen). We read every rival listing's public Store page while pricing it and found none that documents all of those together — though several document some of them, and constant_quadruped/research-grant-aggregator covers more sources than we do (NSF, Grants.gov and USASpending alongside NIH) rather than more depth within RePORTER. Every competitor price above re-verified live 2026-10-06.

Sub-20-user-count cleanup, 2026-10-07. Dropped this file's two remaining bare sub-20 rival user-counts (constant_quadruped/research-grant-aggregator, publicmoney/nih-reporter-grants-scraper, the latter's count having gone stale at 4 vs a live 5) — the price/feature claims in both sentences are unchanged, per the fleet-wide cleanup this file was already queued for.

Named-cohort tier resweep, 2026-10-08. Every one of the 51 listings this sweep matches was re-priced live from its own Apify pricing record (all prices verified 2026-10-08), reading all six subscription tiers of every charge event rather than the Free-tier headline — the fourth consecutive sweep to find no unnamed listing in this niche, and the first to verify the full tier ladder of the already-named cohort in code. No price claim above was contradicted and no rival was found to undercut us that this file does not already disclose: the two that beat our flat $0.0015 on ordinary runs (themineworks/nih-reporter-grants, publicmoney/nih-reporter-grants-scraper) were already published with their full ladders and crossover points, and the cheapest tier of every other RePORTER listing stays above our rate. Two sentences were sharpened rather than corrected, because both stated a true Free-tier price while omitting the ladder beneath it: scrapers_lat/usa-nih-reporter-scraper ($0.012 Free, $0.0102 Gold and above, plus four AI add-on events) and tagadanar/us-grants-monitor (three tiered per-row events, not the single $0.003 rate previously shown). Also re-verified: the "$8–$15 report and export events" figure quoted for the two taroyamada listings is exact across the pair ($8 export and $12 report on the landscape listing, $10 export and $15 report on the publication-output listing), and red.cars/nih-grants-mcp's cheapest of six tool events is $0.03, so the $0.05 per-search figure shown above is not understating it.

Fifth consecutive clean resweep, 2026-10-09 (verified). Own price re-verified first (check-own-price-freshness 24/0, flat $0.0015/result unchanged). A fresh seven-term sweep now matches 48 listings (51 at cycle 1436 — churn, not a term regression) and niche-unnamed finds 0 unnamed of 48, the fifth consecutive audit (1330, 1366, 1404, 1436, 1473) to find this niche fully named. Re-ran the tier-aware batch pricer against the full live cohort (0 unresolvable): the only listings that undercut our flat $0.0015 at any tier are the same six already disclosed above with unchanged ladders and start fees — themineworks/nih-reporter-grants ($0.001 Free → $0.0006 Gold and above, $0.005 start), publicmoney/nih-reporter-grants-scraper ($0.002 Free → $0.0007 Diamond, $0.00005 start), jungle_synthesizer/nih-reporter-grants-publications-scraper ($0.10/run + $0.0005/record), alizarin_refrigerator-owner/nih-grants-api-research-funding-data-for-grants-publications ($0.10/run + $0.00001/row), and the two out-of-scope-dataset listings andrew_avina/sbir-intelligence-mcp and copious_atoll/clinical-trials-scraper. Zero price drift, zero new undercutter, nothing to fix.

Sixth sweep, 2026-10-23 (cycle 2145) — the niche grew and surfaced one real new undercutter. Own price re-verified first (check-own-price-freshness 0 flags, flat $0.0015/result unchanged). A fresh seven-term sweep now matches 61 listings (48 at cycle 1473 — real growth, not term drift) and niche-unnamed finds 12 unnamed of 61 (the first unnamed listings this Actor's sweep has found since cycle 1220), all live-priced. One genuine new undercutter: one_dollar_store/nih-reporter-grants (2 users, filed 2026-10-20 — three days before this audit, a new arrival, not a miss) charges a flat $0.001 per result plus a $0.001 Actor-start fee, verified against its live pricingInfos record — cheaper than our $0.0015 flat rate from row 3 on (break-even at row 2), and strictly cheaper on any run of meaningful size. One more reads RePORTER data and ties our rate without beating it: prixodik/nih-reporter-research-awards-prospect-monitor (2 users) charges $0.0015/row on Free — an exact tie — but stacks a real $0.01 start fee, making it dearer than us at every row count. bigibyte/nih-reporter-grants-scraper (2 users, filed 2026-10-19) carries no live Apify pricing record at all, so it currently runs free on Apify's default model; per the rule applied elsewhere in this file, that makes it nominally the cheapest listing in the sweep, though an unpriced listing this new may simply not have its PPE model configured yet, not a deliberate free offering. Three more are in-scope RePORTER/grants listings but dearer at every tier, not repeated here: v0iddo/nih-reporter-grants ($0.003 Free → $0.0021 Gold and above), afkdigital/newapi24-nih-biotech-research-grants ($0.008 flat), transparent_meteorite/nih-research-grants-awarded ($0.004 flat, plus a nominal $0.00005 start fee). One is in-scope but a different shape, not a substitution risk: remarkable_whipscorpion/nih-territory-award-review is a bounded human-review queue for Type1 administrative award notices, not a general search/export API, and is dearer anyway ($0.005 flat). The remaining five unnamed listings are false matches on the generic sweep terms, reading a different NIH sub-system or an unrelated site entirely, and are out of scope for this README: winterlock/clinicaltrials-scraper, brightish_ascent/clinicaltrials-study-monitor and keepable_injera/clinical-trials-monitor all read ClinicalTrials.gov (already covered by our own clinicaltrials-scraper listing), zerobreak/rxnorm-drug-lookup reads NIH's RxNorm drug vocabulary API, and codecraftco/sec-insider-trades reads SEC Form 4 filings. Fleet-wide check-price-superiority run after the edit: 0 undisclosed.

Notes

Only public data from NIH RePORTER's official API is collected. Issues or feature requests: support@fetchsmith.com. Also available as a hosted API at https://fetchsmith.com

Source code

https://github.com/Fetchsmith/fetchsmith/tree/main/actors/nih-reporter-scraper