DEV Community

Loomwise
Loomwise

Posted on

Search Grants.gov from Python with No API Key: Find Open Federal Grants for Your Nonprofit

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']}")
Enter fullscreen mode Exit fullscreen mode

Three things that will trip you up

  1. Dates are MM/DD/YYYY text. Sorting them as strings puts January 2027 before October 2026. Parse them, as the script does.
  2. 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.
  3. 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 on title in Python.

Make it a weekly digest

  • Run it weekly, store the id values 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)