Disclosure: I'm Ben, and I built the Apify Actor mentioned later in this post. I charge per result for it. The tutorial part needs only Python and the free Grants.gov API.
Every federal grant opportunity (NOFO) is posted on Grants.gov, and Grants.gov has a public JSON API with no key. Hardly anyone uses it directly, partly because the useful parts (award ceiling, total funding, eligibility, agency contact) aren't in the search results. Here's how to get them, and the quirks I hit.
The two endpoints
-
POST https://api.grants.gov/v1/api/search2returns matching opportunities: id, number, title, agency, status, open and close dates. Up torowsper page, withstartRecordNumfor paging. -
POST https://api.grants.gov/v1/api/fetchOpportunityreturns one opportunity's full detail: the synopsis with award ceiling and floor, estimated total funding, expected awards, cost sharing, eligible applicant types, Assistance Listings (CFDA) and the contact.
Step 1: Search, then fetch details
This ran as-is on 25 Oct 2026:
import requests
s = requests.post("https://api.grants.gov/v1/api/search2", json={
"keyword": "artificial intelligence", "oppStatuses": "forecasted|posted", "rows": 3}, timeout=30).json()
print(s["data"]["hitCount"], "matches")
for hit in s["data"]["oppHits"]:
d = requests.post("https://api.grants.gov/v1/api/fetchOpportunity",
json={"opportunityId": int(hit["id"])}, timeout=30).json()["data"]
syn = d.get("synopsis") or {}
print(hit["number"], hit["closeDate"] or "-", syn.get("awardCeiling"), syn.get("agencyContactEmail"))
Output (I've trimmed one individual's email):
167 matches
PD-19-127Y 02/10/2027 none grantsgovsupport@nsf.gov
W519TC-25-S-0001 - None None
G27AS00079 11/14/2026 150000 <agency contact>
Step 2: Filter by who can apply
This is the filter that matters most for nonprofits, universities and small businesses. Grants.gov tags each opportunity with eligible applicant codes, and search2 accepts them pipe-separated in eligibilities. A few: 12 = 501(c)(3) nonprofits, 13 = nonprofits without 501(c)(3), 20 = private institutions of higher education, 06 = public and state-controlled institutions of higher education, 23 = small businesses, 25 = others.
s = requests.post("https://api.grants.gov/v1/api/search2", json={
"oppStatuses": "posted", "eligibilities": "12", "rows": 3}, timeout=30).json()["data"]
print(s["hitCount"]) # 440 on 25 Oct 2026
So on that day, 440 posted opportunities accepted 501(c)(3) applicants.
Step 3: The quirks
-
Inconsistent types.
awardCeilingcame back as the string"none"for the NSF program and as a number for the USGS one. A forecasted DoD notice had no synopsis at all (it lives in a differentforecastblock). Parse defensively and normalise tonull. -
US dates.
closeDateisMM/DD/YYYY, and blank for forecasts and rolling programs. Convert before sorting by "closing soon", or01/07/2028sorts before11/17/2026. -
Counts move daily. My keyword search had 171 matches on 18 Oct and 167 on 25 Oct. Opportunities close, and new ones are posted. Store the opportunity
idand diff, rather than trusting counts. -
One detail call per result. Search is cheap, but anything useful needs
fetchOpportunity. For 400+ results that's 400+ requests. Be gentle and cache. - Contacts are often individuals. The agency contact is sometimes a shared inbox, sometimes a named program officer. Use it to ask about the program, not as a mailing list.
Step 4: Or use the packaged version
I packaged this as an Apify Actor, Grants.gov Scraper – Federal & Nonprofit Grants Finder. It runs the search, fetches every detail, normalises the types and dates, and returns one clean row per opportunity. It also adds daysUntilClose, and "posted in the last N days" / "closing within N days" filters for alerts.
Run-ready link: open it in Apify Console with an example input already filled in ({"keyword": "artificial intelligence", "maxItems": 20}). Click Start, or change the input first.
The pre-filled input (keyword artificial intelligence, forecasted + posted, 20 results) ran on 18 Oct 2026 in about 6 seconds and saved 20 of 171 matches:
| Number | Title | Agency | Deadline | Award ceiling | Total funding |
|---|---|---|---|---|---|
| 26-512 | Unlocking Dataset Value for AI-Enabled Scientific Discovery… | NSF | 2026-11-04 | – | $100M |
| 25-531 | Cybersecurity Innovation for Cyberinfrastructure (CICI) | NSF | 2027-01-20 | $1.2M | $12M |
| G27AS00079 | Cooperative Agreement for Affiliated Partner with the Great… | DOI-USGS1 | 2026-11-14 | $150K | $150K |
The nonprofit version:
{ "statuses": ["posted"], "eligibilities": ["12"], "sortBy": "closingSoon", "maxItems": 10 }
That ran on 19 Oct 2026 and saved 10 of 443 matching opportunities. One was HRSA-27-005, the FY2027 Health Center Service Area Competition, with an award ceiling of $10,116,100, a floor of $650,000, $267,773,700 total, 63 expected awards, and daysUntilClose: 0. Sorting by closing date surfaces exactly the ones you're about to miss.
Each row has: id, number, title, agency, status, open and close dates, days left, CFDA numbers, award ceiling and floor, estimated total funding, expected awards, cost sharing, funding instruments, categories, eligible applicants, description, contact and the grants.gov link.
As a grant alert
Schedule a weekly run with "posted in the last 7 days", your eligibility codes and keywords, and push the new rows to email, Slack or a sheet. That replaces scanning the Grants.gov site by hand.
Limits, honestly
- Federal only. Grants.gov covers federal opportunities. Foundation, corporate and most state grants aren't there.
- It returns what Grants.gov publishes. If an agency leaves the ceiling or funding blank, so does the row. Forecasts usually have no deadline yet.
- Not a full NOFO. Eligibility rules, page limits and review criteria are in the attached documents, which you'll still need to read.
- Partial results. If Grants.gov errors partway through a large run, the Actor keeps what it saved and stops instead of failing the whole run.
- Not affiliated with Grants.gov.
What it costs
$0.003 per opportunity saved ($3 per 1,000), no start fee, and it stops at your maximum cost per run. The 20-result test was 6 cents. If you only need one search, the Python above is free and works fine.
If you look for grants regularly: what do you filter on, and what's missing from Grants.gov's own search? I'd like to know what to add next, so tell me in the comments.
Top comments (0)