# Grants Gov Scraper (`normdata/grants-gov-scraper`) Actor

Scrape US federal grant opportunities from Grants.gov: full funding amounts, eligibility, agency contact, and attachments - by keyword, agency, or opportunity number. The only Grants.gov Actor with a real per-revision change log showing exactly what changed on a listing.

- **URL**: https://apify.com/normdata/grants-gov-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.80 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

![Norm Data](https://i.ibb.co/rGbhM5Y8/Chat-GPT-Image-Sep-8-2026-02-20-50-PM.png)

## Grants.gov Scraper

Get real US federal grant opportunity data from **Grants.gov** with no login: full funding amounts,
eligibility, agency contact, attachments, and a real per-revision change log showing exactly what
changed on a listing - no other Grants.gov Actor surfaces that.

Here is one real row:

```json
{
  "opportunity_id": "361754",
  "opportunity_number": "PA-FPH-27-001",
  "title": "Title X Family Planning Services Grants",
  "status": "posted",
  "agency_name": "Office of the Assistant Secretary for Health",
  "top_agency_name": "Department of Health and Human Services",
  "posted_date": "04/03/2026",
  "response_deadline": "01/11/2027",
  "estimated_total_funding": 257000000,
  "award_ceiling": 22000000,
  "award_floor": 200000,
  "number_of_awards": 90,
  "eligible_applicant_types": [{ "code": "12", "description": "Nonprofits having a 501(c)(3) status with the IRS, other than institutions of higher education" }],
  "assistance_listings": [{ "number": "93.217", "program_title": "Family Planning Services" }],
  "revision_count": 1,
  "last_revision_changed_fields": ["responseDate", "archiveDate", "applicantEligibilityDesc"],
  "url": "https://www.grants.gov/search-results-detail/361754",
  "scraped_at": "2026-09-15T00:00:00.000Z"
}
```

### What it does

Reads Grants.gov's own public API directly and normalizes it into clean, flat rows.

- **Search** filters the live opportunity board by keyword, agency, funding category, funding
  instrument, eligibility, status (forecasted/posted/closed/archived), and posted-date range.
- **Lookup** fetches specific opportunities by their public opportunity number (e.g.
  `PA-FPH-27-001`).
- **Monitor** returns opportunities posted since a given date - built to run on a schedule and catch
  new opportunities as they're published.

Every row can include full funding amounts (estimated total, award ceiling/floor, award count),
eligibility text and applicant-type codes, agency contact info, attachment metadata, and - uniquely

- exactly which fields changed in the opportunity's most recent revision. Missing source values are
  returned as `null`, never invented.

### Why this scraper

- **A real per-revision change log, not just a bare listing.** Grants.gov's own API tracks exactly
  which fields changed each time an opportunity is revised (a due-date push, an eligibility rewrite,
  a funding-amount update) - this Actor surfaces that directly. No other Grants.gov Actor on Apify
  does.
- **Full depth by default, not an expensive add-on.** The strongest existing competitor charges
  $8/1,000 results and requires an extra `includeDetails` toggle to get funding amounts, eligibility,
  and contact info at all - here it's the same request, same price tier.
- **Real filters that actually filter.** Every filter this Actor exposes was checked directly against
  live results (not assumed from Grants.gov's own docs) - including catching that Grants.gov's own
  `postedFrom`/`postedTo` search parameters are silently ignored server-side, so this Actor applies
  that filter itself instead of shipping a broken one.
- **No API key, no login, no browser.** Reads Grants.gov's own public JSON API directly.

### How it compares

Checked directly against the real Grants.gov Actors on Apify (their own declared input/output, not a
guess):

| Capability | This actor | Strongest competitor (full-depth) | Bulk-extract competitor (metadata-only) |
|---|:--:|:--:|:--:|
| Funding amounts, eligibility, contact by default | **yes** | only with an extra paid toggle | no |
| **Real per-revision change log (what changed, not just that something did)** | **yes** | no | no |
| Attachment metadata (NOFO documents) | yes | yes | no |
| A working posted-date filter | **yes** | claimed, not verified to work | n/a |
| Monitor mode (new opportunities on a schedule) | yes | no | no |
| Price per 1,000 full-depth results | **priced below the full-depth competitor** | from $8.00 | n/a (metadata only) |

### Use cases

- **Grant-seeking for universities and nonprofits.** Track new funding opportunities by category,
  agency, or eligibility as soon as they post.
- **Grant-writing consultancies.** Build a filtered, always-current feed for clients in a specific
  field (health, education, research, rural development).
- **Compliance and deadline tracking.** The real per-revision change log catches a due-date push or
  an eligibility rewrite on an opportunity you're already tracking.
- **Federal funding market research.** Analyze award ceilings, funding instruments, and agency
  spending patterns across a whole funding category.

### Quickstart

Search by keyword:

```json
{ "mode": "search", "keyword": "rural broadband" }
```

Search by agency and funding category:

```json
{ "mode": "search", "agencies": ["HHS-OPHS"], "fundingCategories": ["HL"] }
```

Look up specific opportunities:

```json
{ "mode": "lookup", "opportunityNumbers": ["PA-FPH-27-001"] }
```

Monitor new postings in a category, run daily:

```json
{ "mode": "monitor", "postedSince": "1 day", "fundingCategories": ["ED"] }
```

### Input reference

| Field | Applies to | Description |
|---|---|---|
| `maxItems` | all | Ceiling on rows written. |
| `mode` | all | `search`, `lookup`, or `monitor`. |
| `keyword` | search, monitor | Full-text search across titles and descriptions. |
| `agencies` | search, monitor | Exact Grants.gov agency codes (e.g. `HHS-OPHS`). |
| `fundingCategories` / `fundingInstruments` / `eligibilities` | search, monitor | Category/instrument/applicant-type codes. |
| `oppStatuses` | search, monitor | `forecasted`, `posted`, `closed`, `archived`. Defaults to forecasted+posted. |
| `postedFrom` / `postedTo` | search | ISO dates; applied on our side (see FAQ). |
| `fetchDetails` | search, monitor | Adds funding/eligibility/contact/change-log fields. Default on. |
| `opportunityNumbers` | lookup | Public opportunity numbers to fetch directly. |
| `postedSince` | monitor | ISO date or relative window (`"3 days"`). |

### Output reference (selected)

| Field | Description |
|---|---|
| `opportunity_id` / `opportunity_number` / `title` / `url` | Identity. |
| `status` / `category` / `agency_name` / `top_agency_name` | Classification. |
| `posted_date` / `response_deadline` / `deadline_is_estimated` / `archive_date` | Key dates. `deadline_is_estimated` is true on forecasted opportunities, whose deadline isn't confirmed yet. |
| `estimated_total_funding` / `award_ceiling` / `award_floor` / `number_of_awards` | Funding. |
| `eligibility_description` / `eligible_applicant_types` | Who can apply. |
| `funding_instruments` / `funding_categories` / `assistance_listings` | Classification codes, each with its own real description. |
| `contact_name` / `contact_email` / `contact_phone` | Agency contact. |
| `attachments` | Real NOFO/announcement document metadata. |
| `revision_count` / `last_revision_changed_fields` | How many revisions, and exactly what changed most recently. |
| `change_type` | `"new"` in monitor mode. |
| `query` / `error` | Set on an unresolved lookup number. |
| `scraped_at` | ISO 8601 collection timestamp. |

### Run via API and CLI

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~grants-gov-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"search","keyword":"rural broadband"}'
```

```bash
apify call USERNAME/grants-gov-scraper --input '{"mode":"lookup","opportunityNumbers":["PA-FPH-27-001"]}'
```

### Fetch results

```bash
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
```

### Billing and limits

- **Pay per result.** Billed per row written, whether from search, lookup, or monitor.
- **No charge on failure.** A run that produces zero rows fails with a message and is not billed.
- An unresolved lookup number still writes one `error: "not found"` row and is billed as a base row.
- **Free Apify plans** run the built-in 10-row sample only.

### FAQ and troubleshooting

**Do I need an API key?** No. Grants.gov's own public API is read directly, with no key and no
login.

**Why did `postedFrom`/`postedTo` not narrow my results the way I expected?** Confirmed live:
Grants.gov's own search API accepts these parameters but does not actually apply them server-side
(identical result counts with or without them, in every format tried). This Actor filters on the
real `posted_date` itself instead, so the field works correctly here even though the underlying API
silently ignores it.

**Why does an agency code like "HHS" not match anything?** Grants.gov's own agency codes are
sub-agency-specific (e.g. `HHS-OPHS`, not the parent `HHS` alone). Copy the exact code from a real
opportunity's own `agency_code` field, or run a broad search first to find it.

**Does monitor mode catch opportunities that were *modified*, not just newly posted?** It catches
new postings reliably (Grants.gov's own board sorts by posting date, which this Actor walks).
Confirmed live there is no working modified-date sort to walk from for older opportunities that were
recently revised - but every row (in any mode) still carries `last_revision_changed_fields` for the
opportunities it does return, so a tracked opportunity's own latest revision is never hidden from you.

**Why is `response_deadline` sometimes marked `deadline_is_estimated: true`?** That row is a
*forecasted* opportunity - Grants.gov's own heads-up that funding is expected, before it's formally
posted. Its deadline (and its funding amounts and eligibility) are Grants.gov's own real estimates,
not yet confirmed. Once the real solicitation posts, look it up again for the confirmed figures.

**What do the funding instrument/category/eligibility codes mean?** Grants.gov's own API returns a
real human-readable `description` alongside every code - this Actor passes that straight through
rather than re-mapping it against a separate table that could drift out of sync.

**How current is the data?** Read live from Grants.gov on every run. It is not cached.

**Is this an official Grants.gov tool?** No. Independent, not affiliated with Grants.gov or the US
government. It reads only publicly available data.

### Local development

```bash
bun install
bun test                       # offline: normalization, input parsing (real fixtures)
bun run src/main.ts             # reads storage/key_value_stores/default/INPUT.json
```

# Changelog

This Actor's version history is a separate document: https://apify.com/normdata/grants-gov-scraper/changelog.md

# Actor input Schema

## `maxItems` (type: `integer`):

Caps how many rows this run writes. Starts at 10 for a quick sample; raise it for a full run.

## `mode` (type: `string`):

Search filters the live opportunity board by keyword, agency, funding category, eligibility, or date. Lookup fetches specific opportunities by their public number. Monitor returns opportunities posted since a given date - built for a daily/weekly schedule.

## `keyword` (type: `string`):

Full-text search across opportunity titles and descriptions, e.g. "health", "cybersecurity", "rural broadband".

## `agencies` (type: `array`):

Exact Grants.gov agency codes, e.g. "HHS-OPHS", "DOC", "DOD-AFOSR" - copy the code shown on a real opportunity's own agency field, not a general abbreviation (e.g. "HHS" alone will not match its sub-agencies).

## `fundingCategories` (type: `array`):

Category codes, e.g. "HL" (Health), "ED" (Education), "ENV" (Environment), "AG" (Agriculture), "BC" (Business and Commerce). See Grants.gov's own category list for the full set.

## `fundingInstruments` (type: `array`):

Instrument codes: "G" (Grant), "CA" (Cooperative Agreement), "PC" (Procurement Contract), "O" (Other).

## `eligibilities` (type: `array`):

Applicant-type codes, e.g. "00" (State governments), "12" (501(c)(3) nonprofits), "06" (Public/State institutions of higher education), "25" (Others). See a real opportunity's own eligible\_applicant\_types field for the full code list.

## `oppStatuses` (type: `array`):

Which lifecycle stages to include: "forecasted", "posted" (currently accepting applications), "closed", or "archived". Defaults to forecasted + posted (currently-open opportunities) - the same real default Grants.gov's own search uses.

## `postedFrom` (type: `string`):

An ISO date ("2026-09-01"), only keeping opportunities posted on or after it. Applied on our side - confirmed live that Grants.gov's own search API accepts this filter but does not actually apply it.

## `postedTo` (type: `string`):

An ISO date, only keeping opportunities posted on or before it. Applied on our side, same real reason as postedFrom.

## `fetchDetails` (type: `boolean`):

Adds funding amounts, eligibility text, agency contact, attachments, and the real per-revision change log to every row. Off returns only the bare board listing (id, number, title, agency, dates, status) - faster and cheaper, but without the data most people actually want this Actor for.

## `opportunityNumbers` (type: `array`):

Public opportunity numbers, e.g. "PA-FPH-27-001". Case-insensitive exact match against the real number - not a keyword search.

## `postedSince` (type: `string`):

An ISO date ("2026-09-01") or a relative window ("2 days", "1 week"). Returns opportunities posted on or after this date, newest first - run it on a schedule to catch newly-posted opportunities. Combine with a keyword/agency/category filter to monitor a specific area rather than the whole board.

## Actor input object example

```json
{
  "maxItems": 10,
  "mode": "search",
  "keyword": "health",
  "oppStatuses": [
    "forecasted",
    "posted"
  ],
  "fetchDetails": true
}
```

# Actor output Schema

## `listings` (type: `string`):

Complete dataset with all scraped rows.

## `overview` (type: `string`):

Overview view with key fields displayed in a table.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "maxItems": 10,
    "mode": "search",
    "keyword": "health",
    "oppStatuses": [
        "forecasted",
        "posted"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/grants-gov-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "maxItems": 10,
    "mode": "search",
    "keyword": "health",
    "oppStatuses": [
        "forecasted",
        "posted",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/grants-gov-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "maxItems": 10,
  "mode": "search",
  "keyword": "health",
  "oppStatuses": [
    "forecasted",
    "posted"
  ]
}' |
apify call normdata/grants-gov-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,normdata/grants-gov-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JfqElbvDeJIMC5wJj/builds/gp9r99kVk78mdWREA/openapi.json
