# US Federal Grants Monitor (`lowlanddata/grants-gov-monitor`) Actor

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

## Pricing

from $18.50 / 1,000 new grant alerts

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## US Federal Grants Monitor

Watches every federal funding opportunity published on Grants.gov and alerts you the moment one matching your profile appears — a new NIH research announcement, a USDA rural programme, a DOE demonstration grant. It remembers what it has already told you, so an alert always means something changed, never "here is the same list again".

**Live health record:** [lowlanddata.com/status/grants-gov-monitor](https://lowlanddata.com/status/grants-gov-monitor/) - 30-day success rate and daily canary results, failures included. Full input/output reference: [tool page](https://lowlanddata.com/tools/grants-gov-monitor/).

It reads the official Grants.gov API — the same public feed the website itself runs on, no key, no login, no scraping around a defence. Output is institutional by construction: agency, programme, deadlines, award band. Programme-officer names, emails and phone numbers are never extracted, so the rows are safe to store, share and drop into a CRM.

### Quick start (30 seconds, no account keys needed)

1. Hit **Start** with the prefilled `keywords: ["water quality"]` — it runs in a few seconds and shows a baseline of everything currently open on that term.
2. Replace the keywords with your own, or clear them and filter by `agencies` / `fundingCategories` instead. Every run logs the agency codes valid for your search, with counts.
3. Add a **daily schedule** (Actor page → Schedule). From then on, runs are quiet until a genuinely new opportunity appears.

### Who uses this

- **Grant writers at nonprofits** watch `fundingCategories` plus a keyword like `"housing"`, and get the announcement number and `closeDate` the morning it posts, instead of finding it three weeks into a six-week window.
- **University research offices** monitor `agencies: ["HHS-NIH11"]` with `scrapeDetails: true`, and route each alert to the right department using `awardCeilingUsd` and `cfdaNumbers`.
- **Consultants who bid federal work** run several profiles in separate `stateStoreName`s — one per client sector — so each client's digest contains only their opportunities.
- **Municipal finance teams** set `eligibilities: ["02"]` (city and township governments) and `alertOn: "new-and-updated"`, because a deadline that moves matters as much as one that appears.

### What you get

One JSON item per **event**: on the first run a baseline of everything currently matching, then only transitions — a newly posted or forecasted opportunity, and (with `alertOn: "new-and-updated"`) one whose status flipped or whose deadline moved. An alert looks like:

```json
{
  "opportunityId": "357305",
  "opportunityNumber": "PAR-25-274",
  "title": "Feasibility Clinical Trials of Mind and Body Interventions",
  "agency": "National Institutes of Health",
  "agencyCode": "HHS-NIH11",
  "status": "posted",
  "docType": "synopsis",
  "postedDate": "2024-11-21",
  "closeDate": "2026-11-17",
  "cfdaNumbers": ["93.213"],
  "url": "https://www.grants.gov/search-results-detail/357305",
  "isBaseline": false,
  "isUpdate": false
}
```

Field honesty notes:

- `postedDate` and `closeDate` are converted from the API's `MM/DD/YYYY` to ISO. A forecast often has no `closeDate` yet — the field is then absent rather than guessed.
- `description`, `awardCeilingUsd`, `awardFloorUsd`, `costSharingRequired`, `responseDate` and `archiveDate` appear **only** with `scrapeDetails: true`. They come from the opportunity's full synopsis.
- `isUpdate: true` means an opportunity you were already told about changed its status or its deadline. The search feed carries no "last modified" timestamp, so a change to the description text alone will not re-alert — see the coverage note below.
- A failed synopsis fetch never breaks the run: the row still ships with its search fields.

### How much does grants monitoring cost?

Pay per outcome - checking costs nothing; you pay when the monitor tells you something. Three events, each charged for a different thing:

| Event               | Price    | When it is charged                                                                                         |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| **New grant alert** | $0.0199  | A funding opportunity that is genuinely new to your profile - or a status/deadline change, if you opted in |
| **Baseline result** | $0.00199 | Once per opportunity on your **first run only**, while the monitor learns what already exists              |
| **Actor start**     | $0.005   | Per run, whatever it finds                                                                                 |

The split exists because a monitor's rows are not all worth the same. The first run pushes everything currently matching your filters - useful as a snapshot, but nothing you did not know the world contained - so those rows cost a tenth of an alert. From the second run on, you pay the alert price only for real transitions. A run that finds nothing new costs $0.005 total.

**Cost feel:** a profile watching two agencies and a keyword typically baselines at 30-80 opportunities (about $0.06-$0.16, once), then alerts on perhaps 5-30 a month - roughly **$0.15-$0.75/month** including daily scheduled runs. A quiet month is a few cents of start events. Instrumentl and similar grant-discovery tools run $179-899/month against this same public source.

Not sure yet? Set `emitBaseline: false` on the first run to seed the memory silently - you skip the baseline charges entirely and pay only for what appears afterwards.

#### Why an alert costs more than a scraped row

The store headline is per 1,000 events, so alerts read "$19.90 / 1,000" next to raw scrapers at $1-5 per 1,000 rows - but those are different products:

- A **raw scraper** sells rows. To turn rows into alerts you would pull the full opportunity list every day, diff it yourself, and store the state - thousands of rows a month to find the handful that are new.
- **This monitor** sells the transition. It keeps the memory and charges $0.0199 exactly when something is genuinely new to you. Nobody buys 1,000 alerts a month; a busy profile sees a few dozen.

### Not technical? Let your AI assistant set it up

Paste this to Claude, ChatGPT or Cursor along with what you want to watch:

```text
I want to monitor US federal grant opportunities on Apify using the actor
lowlanddata/grants-gov-monitor. Build me the input JSON.

The fields are:
- keywords: array of free-text terms (optional; omit to watch everything matching the other filters)
- agencies: array of Grants.gov agency codes, e.g. ["USDA","DOD"] (optional)
- eligibilities: array of applicant-type codes, e.g. ["02"] (optional)
- fundingCategories: array of category codes (optional)
- fundingInstruments: array of "CA" | "G" | "PC" | "O" (optional)
- oppStatuses: array of "posted" | "forecasted" | "closed" | "archived" (default ["posted","forecasted"])
- scrapeDetails: boolean, adds description and award amounts (default false)
- alertOn: "new" or "new-and-updated" (default "new")
- emitBaseline: boolean (default true)
- maxItems: integer (default 500)
- stateStoreName: string, one per watch profile (default "grants-gov-monitor-state")

Here is what I want to watch: <describe it>

Give me the JSON only. Then tell me how to add a daily schedule on the actor
page, and how to get an email when a run produces items.
```

***

### Sources and honest coverage notes

- **Federal only.** Grants.gov covers US federal funding opportunities. State, county and private-foundation grants are not in this feed and this actor does not invent them.
- **Both synopses and forecasts.** `docType` tells you which. Forecasts are early notice with dates that often move — that is exactly why `alertOn: "new-and-updated"` exists.
- **The official API, unauthenticated.** No login wall, no anti-bot bypass, no rate-limit games. If Grants.gov rejects a search — usually an invalid agency or category code — the run fails with the message the API returned and the valid codes are in the log above it.
- **Amendment detection is coarse.** Status and deadline are the revision marker, because the search feed exposes no modification timestamp. A silently reworded description will not re-alert.

### How the monitor thinks

- **First run = baseline.** Everything currently matching is pushed once, flagged `isBaseline: true`. Set `emitBaseline: false` to seed the memory silently and receive only what appears afterwards.
- **After that, only transitions.** A new opportunity always alerts. An amended one alerts only with `alertOn: "new-and-updated"` — and even when it stays silent, its marker is updated, so switching the toggle on later does not replay months of old changes as fresh news.
- **Changing filters changes the profile.** The memory is keyed by the filter set, so a different keyword or agency list is a different profile and baselines on its own.
- **Memory** lives in a named key-value store (`stateStoreName`, default `grants-gov-monitor-state`) in your account. Use a separate name per watch profile to keep them independent. `resetState: true` wipes it and starts fresh.
- Re-running after a crash re-alerts at most the last few unsaved items — state is checkpointed every 25 rows during the run.

### Input reference

| Field                | Type     | Default                    | Notes                                                                                                                                    |
| -------------------- | -------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `keywords`           | string\[] | `[]`                       | Free text, joined into one search phrase                                                                                                 |
| `agencies`           | string\[] | `[]`                       | Top-level codes (`USDA`, `HHS`) auto-expand to their sub-agencies; exact codes (`USDA-NIFA`) also work. Valid values are logged each run |
| `eligibilities`      | string\[] | `[]`                       | Applicant-type codes                                                                                                                     |
| `fundingCategories`  | string\[] | `[]`                       | Category codes                                                                                                                           |
| `fundingInstruments` | string\[] | `[]`                       | `CA`, `G`, `PC`, `O`                                                                                                                     |
| `oppStatuses`        | string\[] | `["posted","forecasted"]`  | Which statuses to watch                                                                                                                  |
| `scrapeDetails`      | boolean  | `false`                    | Adds synopsis fields; one extra request per alerted row                                                                                  |
| `alertOn`            | string   | `"new"`                    | Or `"new-and-updated"` for status and deadline changes                                                                                   |
| `emitBaseline`       | boolean  | `true`                     | Off = silent first run                                                                                                                   |
| `maxItems`           | integer  | `500`                      | Overflow alerts on the next run                                                                                                          |
| `stateStoreName`     | string   | `grants-gov-monitor-state` | One per watch profile                                                                                                                    |
| `resetState`         | boolean  | `false`                    | Wipe the memory and re-baseline                                                                                                          |

### Scheduling and API use

Daily is the right cadence — Grants.gov posts on business days and nothing is gained by checking hourly. On the Actor page, add a schedule, then set a **Monitoring** alert for "dataset items > 0" so a quiet day stays quiet and a match reaches your inbox.

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/lowlanddata~grants-gov-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"keywords":["water quality"],"oppStatuses":["posted"]}'
```

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lowlanddata/grants-gov-monitor').call({
  agencies: ['USDA'],
  alertOn: 'new-and-updated',
  stateStoreName: 'usda-watch',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient

client = ApifyClient(token=os.environ["APIFY_TOKEN"])
run = client.actor("lowlanddata/grants-gov-monitor").call(run_input={
    "keywords": ["renewable energy"],
    "scrapeDetails": True,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item.get("closeDate"))
```

A typical profile finishes well inside the 300-second synchronous wall. Very broad profiles with `scrapeDetails: true` are better run asynchronously, since the synopsis adds a request per alerted row.

### Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run this monitor as a tool through Apify's hosted MCP server — the agent picks the filters, reads the alerts and summarises them, with no glue code.

Claude Code:

```bash
claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/grants-gov-monitor"
```

Cursor or Claude Desktop (add a custom connector / MCP server with this URL):

```text
https://mcp.apify.com?actors=lowlanddata/grants-gov-monitor
```

Sign in with your Apify account when prompted — runs are billed to it. Setup details per client: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp), or start from this actor's own MCP page: [apify.com/lowlanddata/grants-gov-monitor/api/mcp](https://apify.com/lowlanddata/grants-gov-monitor/api/mcp).

Prompts that work once connected:

- "Which federal grants for coastal water quality opened this week, and when do they close?"
- "Watch USDA rural development opportunities and tell me only about new ones."
- "Summarise the award ceiling and cost-sharing rules for the NIH grants that appeared today."

### Is it legal to monitor federal grant opportunities?

Yes. Grants.gov is the US government's own public announcement channel and publishes a documented API for exactly this purpose — no login wall, no anti-bot measures, nothing circumvented. The whole point of the feed is that opportunities reach applicants.

And the output is safe by construction: no programme-officer names, emails or phone numbers are extracted, so nothing here is personal data (see below).

### Compliance: safe to store, safe to CRM-import

The mapper is a **whitelist**. It names the fields it emits — identifiers, titles, agency, dates, award band, links — and everything else in the API response is dropped by construction rather than filtered afterwards.

That matters here specifically. The Grants.gov detail endpoint returns `agencyContactName`, `agencyContactEmail`, `agencyContactPhone` and a free-text contact block, often naming an individual officer with a direct line. None of them is in the whitelist, and an automated test feeds the mapper a record stuffed with contact data and fails the build if any person-shaped key or value reaches the output.

US federal data sits outside GDPR's scope, but the promise is not jurisdictional: no personal identifiers, by design, everywhere.

Honest limit: `description` is text the agency wrote. If an agency types a person's name into a programme description, that text is passed through as published — we do not rewrite source prose.

### FAQ

**Why did my second run return nothing?** That is the product working. The first run baselines everything currently matching; after that you only hear about opportunities that are new to you. A quiet run means nothing changed.

**Does it collect personal data?** No — by design. Contact names, emails and phone numbers are never extracted, with or without `scrapeDetails`.

**How do I find valid agency or category codes?** Run it once and read the log: every run prints the agency codes valid for your search with their counts, e.g. `HHS(182) DOD(128) USDA(29) NSF(25)`. Top-level codes like `USDA` or `HHS` work directly - the monitor expands them to their sub-agencies for you. Copy codes from the log rather than guessing — and note that a narrow keyword combined with an agency can legitimately return nothing, which is a real empty result, not a bad code.

**Can I watch several unrelated topics?** Yes — give each one its own `stateStoreName`. Profiles with different filters keep separate memories and baseline independently.

**Why didn't I get an alert when a grant's description changed?** Amendment detection uses status and deadline, because the search feed has no modification timestamp. A reworded description alone stays silent.

**What happens if Grants.gov is down?** The run fails loudly with the API's own message rather than reporting an empty result as "nothing new". Your memory is untouched, and the next run catches up.

### Troubleshooting

- **"Grants.gov rejected the search: ..."** — an invalid filter code, almost always an agency or category. The valid codes for your search are logged immediately above the error; copy one from there.
- **"Actor failed: items were found but none carried usable data (title)"** — the API changed shape. Please report it on the Issues tab; it is treated as a break, not a shrug.
- **A baseline that looks too large** — narrow with `agencies` or `fundingCategories`, or set `emitBaseline: false` to seed the memory silently and start from the next new opportunity.

Something else off? Open an issue on the actor's **Issues tab** — reports are read and answered.

And if the alerts are earning their keep, a **rating on this page** takes ten seconds and helps other buyers find a GDPR-clean monitor among the lookalikes.

# Actor input Schema

## `keywords` (type: `array`):

Free-text terms searched across the opportunity. Leave empty to watch everything matching the other filters.

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

Grants.gov agency codes, e.g. USDA, DOD, DOC, ED, DOE. Every run logs the codes valid for your search with their counts - copy them from there rather than guessing.

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

Applicant-type codes, e.g. 02 for city or township governments, 25 for others. The run log lists the codes available for your search.

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

Category codes such as ACA, ENV or HL. Listed in the run log for your search.

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

CA (cooperative agreement), G (grant), PC (procurement contract) or O (other).

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

Which statuses to watch. Default is posted plus forecasted - the two a bidder can act on.

## `scrapeDetails` (type: `boolean`):

Adds the description, award ceiling and floor, cost-sharing flag and response date. Costs one extra request per alerted opportunity, so it is off by default. Contact names, emails and phone numbers are never extracted, with or without this setting.

## `alertOn` (type: `string`):

"new" alerts only on opportunities never seen before. "new-and-updated" also alerts when a seen opportunity changes status or moves its deadline.

## `emitBaseline` (type: `boolean`):

On the very first run, push everything currently matching. Turn off to start silent and receive only what appears afterwards.

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

Upper bound on rows pushed in one run. Anything over the cap alerts on the next run instead.

## `stateStoreName` (type: `string`):

Named key-value store holding what this monitor has already seen. Use a different name to run several independent watch profiles.

## `resetState` (type: `boolean`):

Discard what this profile has seen and start a fresh baseline. Use after changing filters if you want the new profile re-baselined.

## `proxyConfiguration` (type: `object`):

The grants.gov API is public and unauthenticated; the default Apify proxy is plenty.

## Actor input object example

```json
{
  "keywords": [
    "water quality"
  ],
  "oppStatuses": [
    "posted",
    "forecasted"
  ],
  "scrapeDetails": false,
  "alertOn": "new",
  "emitBaseline": true,
  "maxItems": 500,
  "stateStoreName": "grants-gov-monitor-state",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `opportunities` (type: `string`):

Federal grant opportunities matching your profile, one JSON object each. First run: the current baseline; afterwards: only new (or amended) matches.

# 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 = {
    "keywords": [
        "water quality"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lowlanddata/grants-gov-monitor").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 = { "keywords": ["water quality"] }

# Run the Actor and wait for it to finish
run = client.actor("lowlanddata/grants-gov-monitor").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 '{
  "keywords": [
    "water quality"
  ]
}' |
apify call lowlanddata/grants-gov-monitor --silent --output-dataset

```

## MCP server setup

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

```

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/u8ZfUbernaErqYHRD/builds/6klScRD6CmvrtXHlE/openapi.json
