Grants.gov lists every federal grant, 867 open on the day I checked, but its website search is clunky if you check regularly. Grants.gov also has a public search API that needs no key and no account, and almost nobody uses it. This post shows how to call it from Python to build a standing list of open and upcoming grants that match your mission.
Run on Oct 22, 2026, a search for "workforce" grants open to 501(c)(3) nonprofits returned 96 open or forecasted opportunities. The soonest deadlines included NIH genomics research projects (closing Oct 30) and Department of Transportation Small Business Transportation Technical Assistance Centers (closing Oct 31).
The API
POST https://api.grants.gov/v1/api/search2 with a JSON body. Useful fields:
| Field | What it does | Example |
|---|---|---|
keyword |
full-text search | "workforce" |
oppStatuses |
pipe-separated statuses | `"forecasted\ |
{% raw %}eligibilities
|
pipe-separated applicant-type codes |
"12" = 501(c)(3) nonprofits, "25" = others, "00" = state governments |
agencies |
agency code |
"HHS", "DOL", "USDA"
|
rows, startRecordNum
|
paging |
100, 0
|
The script
import requests
from datetime import datetime
SEARCH = "https://api.grants.gov/v1/api/search2" # public, no API key or login
def search_grants(keyword="", eligibilities="", agencies="", rows=100):
"""Return all currently posted (open) and forecasted grants that match."""
out, start = [], 0
while True:
body = {"keyword": keyword, "oppStatuses": "forecasted|posted", "eligibilities": eligibilities,
"agencies": agencies, "rows": rows, "startRecordNum": start}
data = requests.post(SEARCH, json=body, timeout=60).json()["data"]
out += data["oppHits"]
start += rows
if start >= data["hitCount"]:
return out, data
hits, data = search_grants(keyword="workforce", eligibilities="12") # 12 = nonprofits with 501(c)(3) status
print(len(hits), "open or forecasted 'workforce' grants for 501(c)(3) nonprofits")
def close(h): # closeDate is MM/DD/YYYY text, or empty for forecasts
return datetime.strptime(h["closeDate"], "%m/%d/%Y") if h.get("closeDate") else datetime.max
for h in sorted(hits, key=close)[:5]:
print(" -", h["closeDate"] or "(no close date)", h["agencyCode"], h["number"], h["title"][:60],
f"https://www.grants.gov/search-results-detail/{h['id']}")
Three things that will trip you up
-
Dates are
MM/DD/YYYYtext. Sorting them as strings puts January 2027 before October 2026. Parse them, as the script does. -
Forecasts have no close date. Forecasted grants (expected but not yet open) usually have an empty
closeDate. Don't drop them: forecasts give you months of lead time to line up partners. -
Keywords are broad. "Workforce" also matches research grants that mention the workforce in passing. Narrow with
agencies(DOL for job training, for example) or add a second keyword filter ontitlein Python.
Make it a weekly digest
- Run it weekly, store the
idvalues you've seen, and email yourself only the new ones. - Run several searches (one per program area) and merge them on
id. - The detail page
https://www.grants.gov/search-results-detail/{id}has the full notice and application package.
Optional: I run this as a hosted, scheduled search that combines Grants.gov with SAM.gov contract notices: Open Federal Grants (ready-made search) (paid per result). Disclosure: it's my tool. The script above does the same job for free.
Top comments (0)