Grants.gov API: search federal grant opportunities from code, no key needed
Grants.gov has a public REST API for finding funding opportunities, and the two calls most people need don't require any authentication: search2 to find opportunities and fetchOpportunity to get the details of one.
The endpoints
Both are POST requests with a JSON body:
| Call | URL | Body |
|---|---|---|
| Search | https://api.grants.gov/v1/api/search2 |
{"keyword": "...", "oppStatuses": "forecasted\|posted", "rows": 100, "startRecordNum": 0} plus optional agencies, eligibilities, fundingCategories, aln, oppNum |
| Detail | https://api.grants.gov/v1/api/fetchOpportunity |
{"opportunityId": 289999} |
Grants.gov's docs state that authentication isn't required for either call.
Gotchas
- Check
errorcode, not just the HTTP status. Responses wrap the data as{"errorcode": 0, "msg": "Webservice Succeeds", "data": {...}}. Treat anything other thanerrorcode: 0as a failure. - Multiple values are pipe-separated strings. For example
"oppStatuses": "forecasted|posted". The statuses areforecasted,posted,closedandarchived. - Paging is
rows+startRecordNum.data.hitCountis the total. Loop until you've readhitCountresults or a page comes back empty. - Dates come in several formats. Search hits use
MM/DD/YYYYforopenDateandcloseDate, andcloseDatecan be empty. Detail records use strings like"Oct 11, 2023 05:17:55 PM EDT"and"2023-10-11-00-00-00". Normalise before comparing. - Forecasts and synopses are different objects. In
fetchOpportunity, a posted opportunity has asynopsisobject and a forecast has aforecastobject. Award ceiling, floor, eligible applicants and contacts live inside whichever one is present. - Search results are thin. A search hit gives you ID, number, title, agency, dates, status and assistance listing numbers. For money and eligibility, call
fetchOpportunityper ID, and keep the request rate modest.
Working example
import requests
API = "https://api.grants.gov/v1/api"
def post(endpoint, body):
r = requests.post(f"{API}/{endpoint}", json=body, timeout=30)
r.raise_for_status()
data = r.json()
if data.get("errorcode") != 0: # failures can still come back as HTTP 200
raise RuntimeError(data.get("msg"))
return data["data"]
hits, start = [], 0
while True:
page = post("search2", {"keyword": "broadband", "oppStatuses": "forecasted|posted",
"rows": 100, "startRecordNum": start})
hits += page["oppHits"]
start += 100
if start >= page["hitCount"] or not page["oppHits"]:
break
for h in hits[:5]:
detail = post("fetchOpportunity", {"opportunityId": int(h["id"])})
s = detail.get("synopsis") or detail.get("forecast") or {}
print(h["openDate"], h["closeDate"] or "-", h["number"], h["title"][:60],
"| ceiling:", s.get("awardCeiling"))
print(len(hits), "matching opportunities")
That prints the first five matches with their award ceiling (if there is one) and the total count. To build a daily "new grants" feed, store the IDs you've seen and alert only on new ones.
Grants.gov is separate from contract opportunities, which live on SAM.gov. See SAM.gov API: how it works for that side.
We built this
Disclosure: Your Evenings Ltd built and sells this. It isn't an official government product and isn't affiliated with Grants.gov or any agency. SAM.gov & Grants.gov Scraper on the Apify Store wraps both calls for you:
- It reads Grants.gov live during each run with
search2andfetchOpportunity, with no key, for posted and forecasted grants. - It returns award ceiling and floor, estimated funding, expected number of awards, assistance listings, funding categories and eligible applicant types in a flat row.
- It filters by keyword (using Grants.gov's own search), agency and posted or due date, with agency and dates checked in code.
- It can return SAM.gov contract notices in the same schema in the same run.
monitorModeon a daily schedule returns only grants you haven't seen before. Every row hassource_updated_at.
FAQ
Does the Grants.gov API need an API key?
Not for search2 or fetchOpportunity. Grants.gov's documentation says authentication isn't required for them.
How do I page through Grants.gov search results?
Send rows and startRecordNum in the body, and stop when you've read data.hitCount results.
How do I get the award ceiling for a grant?
Call fetchOpportunity with the ID, and read awardCeiling from the synopsis or forecast object.
Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them. This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.