Nexus Tools / Data guides

US federal grant opportunities as JSON or CSV: the Grants.gov API, a Python example and a free sample

How to pull federal grant opportunities from Grants.gov yourself, which filters silently return nothing, how to get amounts and the real deadline, what the terms of service require, and when a normalized feed is worth paying for.

Updated: November 2026. API behaviour below was tested live on 5 November 2026.

This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.

The short version

1. The Grants.gov API

Documentation: grants.gov/api/api-guide; the guide states that authentication is not required for search2 and fetchOpportunity. Production host https://api.grants.gov. Both endpoints take POST with a JSON body; a plain GET returns HTTP 403 with an empty body, which is just the gateway.

curl -s -X POST https://api.grants.gov/v1/api/search2 \
  -H "Content-Type: application/json" \
  -d '{"keyword": "", "oppStatuses": "posted", "sortBy": "openDate|desc", "rows": 2}'

The response has data.hitCount (858 posted opportunities on 5 November 2026) and data.oppHits[]. One hit, as returned:

{"id": "364212", "number": "NHTSA-693JJ927R000007-FY27",
 "title": "State Process for Informing Consumers of Recalls Program",
 "agencyCode": "DOT-NHTSA", "agency": "National Highway Traffic Safety Administration",
 "openDate": "11/04/2026", "closeDate": "02/04/2027", "oppStatus": "posted",
 "docType": "synopsis", "cfdaList": ["20.614"]}

Each response also carries facet blocks (agencies, eligibilities, funding categories) and a token, so it weighs 11 to 14 KB regardless of rows.

Counts by status on 5 November 2026

oppStatusesOpportunities
posted858
forecasted588
closed8,670
archived73,555

Without oppStatuses, the default is posted plus forecasted. Pass several statuses pipe-separated: "posted|forecasted". An unknown status or filter value returns 0 hits, not an error.

Filters: what works and what silently returns zero

GoalWhat workedTrap
Agency"agencies": "HHS-NIH11" or "HHS*" (420 posted)A bare department code HHS returned 0. Use the exact code or a trailing wildcard.
ALN / CFDA number"cfda": "93.855", or a prefix such as 93.5*Only one value: "93.855|93.859" returned 0 (so did a comma list).
Posted in the last N days"dateRange": 30; other integers also workedCounts days since posting, not a date range.
Sort"sortBy": "openDate|desc" or "closeDate|asc"-
Keyword"keyword": "cybersecurity"Several words behave as AND; we did not find a phrase operator.
Funding instrument, category, eligibilitypipe-separated code lists (OR)Eligibility uses numeric codes from the facet block.

Paging

The detail call

POST /v1/api/fetchOpportunity with {"opportunityId": 364212} returns a synopsis block (posted, closed, archived) and/or a forecast block, plus the ALN programs, agency details, attachments and application packages. Things to know:

The daily XML extract, for bulk loads

grants.gov/xml-extract lists the last seven daily files, GrantsDBExtract<YYYYMMDD>v2.zip, 78.4 MB each, rebuilt at about 04:40 US Eastern (the 4 November file is stamped 04:43:01 AM EST). One request gives you everything with amounts, instead of one detail call per opportunity. The cost is a bigger download and an XML parse, which is a poor trade for a small query and a good one for a full load.

2. DIY: posted opportunities to CSV in Python

This script (tested against the live API on 5 November 2026: 858 posted opportunities, 10 rows written) searches, fetches the detail of each hit, converts the string amounts and text dates and writes a CSV. It needs pip install requests.

import csv
import re
import time
from datetime import datetime
from html import unescape

import requests

BASE = "https://api.grants.gov/v1/api"
S = requests.Session()
S.headers["User-Agent"] = "my-grants-script/1.0"


def post(path, body):
    for attempt in range(5):
        r = S.post(f"{BASE}/{path}", json=body, timeout=60)
        if r.status_code in (429, 500, 502, 503, 504):
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()
        j = r.json()
        if j.get("errorcode", 0) != 0:                  # errors can come back as HTTP 200
            raise RuntimeError(j.get("msg"))
        return j["data"]
    raise RuntimeError("Grants.gov unavailable")


def money(v):
    """Amounts are strings: "1500000", "none", "" or missing."""
    try:
        return float(v)
    except (TypeError, ValueError):
        return None


def day(s):
    """'Feb 04, 2027 12:00:00 AM EST' -> '2027-02-04'"""
    try:
        return datetime.strptime(s[:12], "%b %d, %Y").date().isoformat() if s else None
    except ValueError:
        return None


# 1) Search: no key needed. Dates in the hits are MM/DD/YYYY. The agency filter needs "HHS*" or "HHS-NIH11", not "HHS".
hits = post("search2", {"keyword": "", "oppStatuses": "posted", "agencies": "",
                        "sortBy": "openDate|desc", "rows": 10, "startRecordNum": 0})
print(hits["hitCount"], "posted opportunities")

rows = []
for h in hits["oppHits"]:
    # 2) One detail call per opportunity gives description, amounts, eligibility, the real deadline.
    d = post("fetchOpportunity", {"opportunityId": int(h["id"])})
    syn = d.get("synopsis") or d.get("forecast") or {}
    desc = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", unescape(syn.get("synopsisDesc") or syn.get("forecastDesc") or ""))).strip()
    rows.append({
        "id": h["id"], "number": h["number"], "title": h["title"], "agency": h["agency"],
        "posted": day(syn.get("postingDate")), "deadline": day(syn.get("responseDate")),
        "total_funding": money(syn.get("estimatedFunding")), "award_ceiling": money(syn.get("awardCeiling")),
        "aln": ";".join(h.get("cfdaList") or []), "description": desc[:300],
        "url": f"https://www.grants.gov/search-results-detail/{h['id']}",
    })
    time.sleep(0.3)                                      # no documented limit; the terms allow blocking clients that exceed or circumvent limits

with open("grants_gov.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=rows[0].keys())
    w.writeheader()
    w.writerows(rows)
print(len(rows), "rows written")

3. When the Actor saves time

For a one-off export, the script is enough and free. The Apify Actor Grants.gov Opportunities (by Nexus Scrapers) wraps the same two official endpoints. It pays off when you would otherwise maintain that code:

Pricing is pay per result; the Actor page shows the current price.

Limits: it does not use the XML extract, so a full-history dump is not its job; amount filters need one detail call per opportunity (it examines up to 500); it does not submit applications or download application packages; the deadline is a date only. If you need a single list, use the script. This guide is independent: This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.

4. Real sample: 20 normalized records

These 20 records were produced on 5 November 2026 by running the Actor's own code locally on live API responses (no Apify platform involved): 16 posted opportunities (newest first, at most two per agency) and 4 forecasts. They are the first matches in that order, not hand-picked for quality. Contacts are not included; a scrub pass over every text field found no e-mail address or phone number to remove. Descriptions are shortened to 1,500 characters and converted from HTML to text, which is the Actor's only change to the content; amounts and dates are reformatted. One forecast has no funding amount (null), as published.

Download: sample.json (full records, UTF-8) and sample.csv (flattened, lists joined with | , opens in Excel). Free to use under the notes in section 5.

OpportunityStatusAgencyTitleFirst ALNFundingDeadline
364212postedNational Highway Traffic Safe…State Process for Informing Consumers of Recalls Pr…20.6141,500,000 USD2027-02-04
362952postedNational Institutes of HealthSystems Biology of Infection and Inflammation in Ch…93.12126,000,000 USD2027-02-18
364213postedACC-APG-Aberdeen Division AAPEX Accelerator-Option Period for Existing Award R…12.0021,500,000 USD2027-07-04
364217postedFood and Drug AdministrationDevelopment and Maintenance of Human and Animal Foo…93.1036,500,000 USD-
364218postedFood and Drug AdministrationNational Produce Safety Partnership Program93.10334,000,000 USD-
360551postedNational Institutes of HealthGeorge M. O’Brien Urology Cooperative Research Cent…93.KRC5,200,000 USD2027-02-01
364205postedNational Aeronautics and Spac…Fiscal Year 2027 Teams Engaging Affiliated Museums…43.0084,000,000 USD2027-02-01
364197postedBureau of Justice AssistanceOJP FY 2026 Special Attorneys Program Round 1116.0763,000,000 USD2026-11-17
364196postedGeological SurveyUSGS Cooperative Landslide Hazard Mapping and Asses…15.8211,000,000 USD2027-02-01
364202postedEnvironmental Protection Agen…FY27 Guidelines for Brownfield Cleanup Grants66.81814,000,000 USD2027-01-21
364201postedEnvironmental Protection Agen…FY27 Guidelines for Brownfield Revolving Loan Fund…66.81810,000,000 USD2027-01-21
364195postedNational Endowment for the Hu…Endowments for Advancing the Humanities45.04310,000,000 USD2027-01-13
364190postedGeological SurveyCooperative Agreement for affiliated Partner with R…15.808490,000 USD2026-12-02
364134postedAdministration for Children a…Residential Services for Unaccompanied Alien Childr…93.676280,000,000 USD2026-12-29
364174postedNational Energy Technology La…Grid Supply Chain Alternatives to Lead Energy Domin…81.122237,000,000 USD2027-02-08
364188postedNational Aeronautics and Spac…Established Program to Stimulate Competitive Resear…43.01621,000,000 USD2027-02-01
364173forecastedSubstance Abuse and Mental He…First Responders-Comprehensive Addiction and Recove…93.49020,000,000 USD-
364156forecastedAdministration for Community…National Center for Law and Elder Rights93.0481,500,000 USD-
364150forecastedAdministration for Community…University Centers for Developmental Disabilities i…93.63226,419,764 USD-
364123forecastedNational Institutes of HealthLearning Disabilities Research Centers93.865--

One opportunity in full (description omitted):

{
  "sourceId": "364213",
  "url": "https://www.grants.gov/search-results-detail/364213",
  "noticeType": "contract-notice",
  "noticeTypeCode": "synopsis",
  "title": "APEX Accelerator-Option Period for Existing Award Recipients",
  "buyer": {
    "name": "ACC-APG-Aberdeen Division A",
    "country": "US",
    "city": null,
    "nutsCode": null,
    "buyerType": "central-government"
  },
  "estimatedValue": {
    "amount": 1500000,
    "currency": "USD"
  },
  "publicationDate": "2026-11-04",
  "submissionDeadline": "2027-07-04",
  "grantsOpportunityNumber": "DOW2027APEXACCELERATORCLOSED",
  "grantsStatus": "posted",
  "grantsAgencyCode": "DOD-AMC-ACCAPGADA",
  "grantsAln": [
    {
      "code": "12.002",
      "title": "Procurement Technical Assistance For Business Firms"
    }
  ],
  "grantsCategory": {
    "code": "D",
    "description": "Discretionary"
  },
  "grantsFundingInstruments": [
    {
      "id": "CA",
      "description": "Cooperative Agreement"
    }
  ],
  "grantsEstimatedValueBasis": "awardCeiling",
  "grantsEstimatedTotalFunding": null,
  "grantsAwardCeiling": {
    "amount": 1500000,
    "currency": "USD"
  },
  "grantsAwardFloor": {
    "amount": 60000,
    "currency": "USD"
  },
  "grantsExpectedAwards": 88,
  "grantsCostSharing": true
}

Read the "Funding" column with care: it is total program funding where the announcement states one, and the award ceiling otherwise (the grantsEstimatedValueBasis field says which). It is not what one applicant receives.

5. Data licence and terms

The data is public US federal government information published by Grants.gov, operated by the U.S. Department of Health and Human Services (HHS) Office of Grants. There is no separate data licence; use of the API is governed by the Grants.gov API Terms of Service. In short:

This is a summary, not legal advice; the terms can change, so read the page itself. Personal data: contact names, e-mails and phones in announcements are officials' business contacts; our sample omits them.

Links