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.
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
- Grants.gov lists US federal grant opportunities. Its official API has two
POSTendpoints that need no authentication:search2(the list) andfetchOpportunity(the detail of one opportunity). The terms of service allow you to "search, display, analyze, retrieve, view and otherwise get" the data, and ask for a visible non-endorsement notice (quoted above and in section 5). - The search response is only a thin list. Amounts, eligibility, description and the real closing date need one extra call per opportunity. Several filters behave in ways that are not obvious: a bare department code like
HHSmatches nothing, only one ALN number can be filtered at once, and amounts are strings. - For a bulk or full-history load there is also a daily XML extract (about 78 MB). This page shows both, a script tested today and a free 20-record sample of normalized output.
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
oppStatuses | Opportunities |
|---|---|
| posted | 858 |
| forecasted | 588 |
| closed | 8,670 |
| archived | 73,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
| Goal | What worked | Trap |
|---|---|---|
| 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 worked | Counts 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, eligibility | pipe-separated code lists (OR) | Eligibility uses numeric codes from the facet block. |
Paging
rowsis not capped at 100:rows: 5000returned all 858 posted hits in one response.startRecordNumpages, and a start past the end returns an empty list.- There is no "updated since" filter, so to detect changes you must keep what you saw and compare.
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:
- Amounts are strings:
"1500000","none", an empty string or absent.estimatedFunding(total program funding) andawardCeilingare different things; the award ceiling alone understates a big program. - Dates are text:
"Feb 04, 2027 12:00:00 AM EST". The midnight is a placeholder; the true cut-off time is in the announcement text. The search list uses another format,MM/DD/YYYY. - Descriptions are HTML.
- Errors can arrive as HTTP 200. An unknown id returns
errorcode 0and a "no record found" message; check the body. - The contact fields can hold a person. The detail record carries a contact name, e-mail and phone; usually a program mailbox, sometimes a named officer. And
synopsis.agencyNamecan contain that person's name instead of the agency, as it did for the NHTSA opportunity above, so take the agency from the search hit. - A record posted after a forecast carries both blocks;
docTypetells you which is current. - About 0.8 to 1.2 seconds per detail call. We found no documented rate limit; the terms reserve the right to block clients that "exceed or circumvent" limits, so keep a low concurrency (we use 4 in flight at about 5 starts per second).
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:
- One normalized schema shared with our TED, UK, SAM.gov and USAspending Actors: money as
{amount, currency}(numbers, never"none"strings), dates asYYYY-MM-DD, a buyer object, missing values asnull. Grant-specific data (opportunity number, status, ALN programs, funding instruments, eligibility, cost sharing, award floor and ceiling, attachments) sits in extragrants*keys, and the funding basis (total program funding or award ceiling) is named in the record. - Detail merge in one run: list and detail calls, polite pacing and retries handled, HTML turned into text (shortened to 1,500 characters).
- Filters as plain inputs: keyword, statuses, agencies (department code or exact code, the wildcard handled for you), ALN, categories, eligibilities, instruments, posting window, "closing within N days" and a funding range.
- Contacts are off by default (an
includeContactinput adds them as published). - Scheduling and integrations through Apify: daily runs into Slack, e-mail, Google Sheets or a webhook; export as JSON, CSV or Excel; API and MCP access.
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.
| Opportunity | Status | Agency | Title | First ALN | Funding | Deadline |
|---|---|---|---|---|---|---|
| 364212 | posted | National Highway Traffic Safe… | State Process for Informing Consumers of Recalls Pr… | 20.614 | 1,500,000 USD | 2027-02-04 |
| 362952 | posted | National Institutes of Health | Systems Biology of Infection and Inflammation in Ch… | 93.121 | 26,000,000 USD | 2027-02-18 |
| 364213 | posted | ACC-APG-Aberdeen Division A | APEX Accelerator-Option Period for Existing Award R… | 12.002 | 1,500,000 USD | 2027-07-04 |
| 364217 | posted | Food and Drug Administration | Development and Maintenance of Human and Animal Foo… | 93.103 | 6,500,000 USD | - |
| 364218 | posted | Food and Drug Administration | National Produce Safety Partnership Program | 93.103 | 34,000,000 USD | - |
| 360551 | posted | National Institutes of Health | George M. O’Brien Urology Cooperative Research Cent… | 93.KRC | 5,200,000 USD | 2027-02-01 |
| 364205 | posted | National Aeronautics and Spac… | Fiscal Year 2027 Teams Engaging Affiliated Museums… | 43.008 | 4,000,000 USD | 2027-02-01 |
| 364197 | posted | Bureau of Justice Assistance | OJP FY 2026 Special Attorneys Program Round 11 | 16.076 | 3,000,000 USD | 2026-11-17 |
| 364196 | posted | Geological Survey | USGS Cooperative Landslide Hazard Mapping and Asses… | 15.821 | 1,000,000 USD | 2027-02-01 |
| 364202 | posted | Environmental Protection Agen… | FY27 Guidelines for Brownfield Cleanup Grants | 66.818 | 14,000,000 USD | 2027-01-21 |
| 364201 | posted | Environmental Protection Agen… | FY27 Guidelines for Brownfield Revolving Loan Fund… | 66.818 | 10,000,000 USD | 2027-01-21 |
| 364195 | posted | National Endowment for the Hu… | Endowments for Advancing the Humanities | 45.043 | 10,000,000 USD | 2027-01-13 |
| 364190 | posted | Geological Survey | Cooperative Agreement for affiliated Partner with R… | 15.808 | 490,000 USD | 2026-12-02 |
| 364134 | posted | Administration for Children a… | Residential Services for Unaccompanied Alien Childr… | 93.676 | 280,000,000 USD | 2026-12-29 |
| 364174 | posted | National Energy Technology La… | Grid Supply Chain Alternatives to Lead Energy Domin… | 81.122 | 237,000,000 USD | 2027-02-08 |
| 364188 | posted | National Aeronautics and Spac… | Established Program to Stimulate Competitive Resear… | 43.016 | 21,000,000 USD | 2027-02-01 |
| 364173 | forecasted | Substance Abuse and Mental He… | First Responders-Comprehensive Addiction and Recove… | 93.490 | 20,000,000 USD | - |
| 364156 | forecasted | Administration for Community… | National Center for Law and Elder Rights | 93.048 | 1,500,000 USD | - |
| 364150 | forecasted | Administration for Community… | University Centers for Developmental Disabilities i… | 93.632 | 26,419,764 USD | - |
| 364123 | forecasted | National Institutes of Health | Learning Disabilities Research Centers | 93.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:
- You may use the API to "search, display, analyze, retrieve, view and otherwise get" information from grants data.
- Attribution is required. Services that use the API should display this notice prominently: This product uses the Grants.gov API but is not endorsed or certified by the U.S. Department of Health and Human Services.
- You may use the Grants.gov name to identify the source, but not to imply endorsement.
- You may not modify or falsely represent the content and still claim HHS Grants.gov as the source. Shortening a description or reformatting a date is presentation; do not alter the meaning of an announcement. Always link back to the opportunity page.
- Grants.gov may limit access and may block clients that attempt to exceed or circumvent limits. The API is provided as is; the terms also contain warranty, liability and indemnification clauses.
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.