# Grants Monitor - Grants.gov + EU Funding Calls Keyword Alerts (`datadeltas/grants-monitor`) Actor

Scheduled change feed of new grant funding opportunities from grants.gov (US federal) and the EU Funding & Tenders portal. Filter by your own keywords, funding categories and award size;

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

## Pricing

from $15.00 / 1,000 grant opportunity detecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Grants Monitor — Grants.gov + EU Funding Calls Keyword Alerts

The **new-opportunity feed** for a grant office. Every scheduled run searches
**grants.gov** (US federal) and the **EU Funding & Tenders portal** (Horizon
Europe, DIGITAL, CEF, EuropeAid and the rest), keeps only the calls that
match your keywords, and emits **just the ones it has never seen before**.

Other actors dump the whole grants.gov catalogue and leave you to work out
what changed. This one remembers. Run it on a schedule and the dataset from
each run *is* your circulation list: the calls that appeared since you last
looked, with synopsis, award ceiling, funder and deadline attached.

Both sources are official, keyless APIs — no scraping of aggregators, no
login, no API key for you to obtain or renew.

### What you get

| | grants.gov | EU Funding & Tenders |
|---|---|---|
| Coverage | All US federal grant opportunities: posted **and** forecasted | All calls for proposals + EuropeAid calls, **open and forthcoming** |
| Stable id | Opportunity number (`EPA-OW-OWM-26-03`) | Topic identifier (`HORIZON-CL6-2026-04-GOVERNANCE-01`) |
| Award amounts | Award ceiling + floor where published | Not published by the portal — always `null` |
| Typical volume | ~90–120 new postings per fortnight | ~600 open + ~620 forthcoming calls |

### Example output record

```json
{
  "source": "grants-gov",
  "opportunityId": "O-BJA-2026-172719",
  "title": "OJP FY 2026 Special Attorneys Program Round 8",
  "agency": "Bureau of Justice Assistance",
  "synopsis": "The Special Attorneys Program provides funding to support the placement of attorneys in prosecutors' offices to prosecute violent crime…",
  "awardCeiling": 3000000.0,
  "awardFloor": 0.0,
  "postedDate": "2026-08-28",
  "closeDate": "2026-09-14",
  "category": "Law, Justice and Legal Services",
  "url": "https://grants.gov/search-results-detail/363733",
  "detectedAt": "2026-08-31T11:20:41Z"
}
```

An EU record has the same shape, with `source: "eu-funding-portal"`,
`agency: "European Commission"`, and `awardCeiling`/`awardFloor` as `null`:

```json
{
  "source": "eu-funding-portal",
  "opportunityId": "HORIZON-WIDERA-2027-05-ERA-04",
  "title": "Pillar III: Promoting public engagement in R&I and scientific literacy",
  "agency": "European Commission",
  "synopsis": "Proposals should support the development of citizens' scientific literacy and their engagement in research and innovation…",
  "awardCeiling": null,
  "awardFloor": null,
  "postedDate": "2026-12-08",
  "closeDate": "2027-03-11",
  "category": "HORIZON Coordination and Support Actions",
  "url": "https://ec.europa.eu/info/funding-tenders/opportunities/portal/screen/opportunities/topic-details/HORIZON-WIDERA-2027-05-ERA-04",
  "detectedAt": "2026-08-31T11:31:07Z"
}
```

`opportunityId` is each portal's own public reference, so it pastes straight
into your grants pipeline, your board paper or the portal's search box.
Every field is `null` rather than invented when a portal does not publish it
— a grants.gov *forecast*, for instance, usually has no deadline and no award
ceiling yet.

### Input options

| Input | Default | What it does |
|---|---|---|
| `sources` | both | `grants-gov`, `eu-funding-portal`, or both. |
| `keywords` | *(empty)* | Case-insensitive match on **title + full synopsis**; a call is emitted if **any** keyword matches. Empty = every new call. |
| `categories` | *(empty)* | grants.gov funding-activity categories (Education, Health, Environment, …). No effect on EU calls. |
| `fundingInstruments` | *(empty)* | grants.gov instruments — pick **Grant** alone to exclude cooperative agreements and procurement contracts. |
| `euStatuses` | open + forthcoming | Include EU calls that are accepting applications now, announced for later, or both. |
| `minAwardCeiling` | *(none)* | Minimum award ceiling in USD. **grants.gov only** — the EU portal publishes no per-award ceiling, so EU calls are never dropped by it. |
| `sinceDays` | 14 | How far back to look. Your backfill on run one. |
| `maxItems` | 500 | Cap on opportunities emitted (and billed) per run. Split **fairly** across the sources in play, so grants.gov cannot crowd out the EU portal; the overflow carries to the next run. |
| `stateStoreName` | `grants-monitor-state` | Change it to run two independent watchlists from one actor. |

#### Keyword matching, precisely

Keywords are matched as plain case-insensitive substrings against the
opportunity's **title and its full published description** — not the trimmed
`synopsis` in the output, and not the funder's own fuzzy relevance ranking.
That is deliberate: it is narrower and far more predictable than the portals'
own search boxes, which stem words and also index attachments, eligibility
notes and boilerplate. Searching grants.gov for *literacy* over one fortnight
returns 9 hits; only 2 of them actually say "literacy" in the title or
synopsis, and those 2 are what this actor emits. If you want the wide net,
use several narrow keywords rather than one broad one.

### The first run backfills, then it goes quiet

Run one emits everything in your `sinceDays` window that matches — a real
backfill you can circulate immediately. From run two on, an empty dataset
means nothing new was published, which is the answer you wanted.

EU **forthcoming** calls always fall inside the window because their opening
date is in the future, so run one also hands you the announced EU pipeline —
months of advance notice on calls that have not opened yet. That makes the
first run the big one (a no-keyword backfill emitted 389 records: 117 from
grants.gov, 272 from the EU portal). Set `maxItems` low on the first run if
you want to meter it; anything over the cap is **not** marked as seen and is
emitted on the following run instead of being lost.

Recommended schedule: **daily** or **twice weekly**. grants.gov posts on
business days; EU calls arrive in work-programme batches.

### Use cases

- **"Tell me every new federal and EU call that mentions our programme
  areas."** Set `keywords` to your areas — `["early childhood", "literacy",
  "food security"]` — schedule it daily, and pipe the dataset into Slack or a
  shared inbox. That is the Monday-morning scan, done.
- **"We only bid on awards above $250k."** Set `minAwardCeiling: 250000` and
  `fundingInstruments: ["G"]` to strip out small awards, cooperative
  agreements and contracts, so the development team sees only opportunities
  worth writing for.
- **"Give research services 6 months' warning on Horizon calls."** Set
  `sources: ["eu-funding-portal"]` with `euStatuses: ["forthcoming"]` and your
  faculty keywords, and every newly announced topic lands before the call
  opens — while there is still time to build a consortium.

### Pricing (pay per event)

| Event | Price | Charged |
|---|---|---|
| Actor start | $0.005 | Once per run, flat |
| Grant opportunity detected | $0.015 | Per new opportunity pushed to the dataset |

You pay for changes, not for pages. A quiet day costs half a cent.

### Maintenance promise

Both APIs are checked against a live smoke run and a fixture-based test suite
(38 tests) on each release; parsing degrades to a logged warning rather than a
failed run when a portal changes a field. If one portal is down for a run, the
other still reports and the run succeeds with a warning — only a total outage
fails the run, deliberately, so a dead run never looks like a quiet day.

### Changelog

- **0.1** — First release. grants.gov (Search2 + fetchOpportunity) and the EU
  Funding & Tenders portal (SEDIA search), seen-id delta monitoring per
  source, keyword / category / instrument / award-size filters.

# Actor input Schema

## `sources` (type: `array`):

Which official portals to watch. Default: both. If one portal is down for a run, the other still reports and the run succeeds with a warning.

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

Case-insensitive substring match on each opportunity's title AND synopsis; an opportunity is emitted when ANY keyword matches. Use the terms your programme staff already search with, e.g. "literacy", "food security", "early childhood", "cancer screening". Leave empty to monitor every new call from the selected sources.

## `categories` (type: `array`):

Optional grants.gov funding-activity categories, applied server-side. Has no effect on EU calls, which the portal does not classify this way.

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

Optional grants.gov instrument filter, applied server-side. Pick "Grant" alone to exclude cooperative agreements and procurement contracts. Has no effect on EU calls.

## `euStatuses` (type: `array`):

Which EU calls to include. "Open" = accepting applications now. "Forthcoming" = published with a future opening date, which is how a grant office gets months of lead time. Default: both.

## `minAwardCeiling` (type: `integer`):

Optional floor on the maximum award size, e.g. 250000 to ignore small awards. Applies to grants.gov only: a grants.gov opportunity that publishes no award ceiling (most forecasts) is skipped when this is set. EU calls are never dropped by this filter because the EU portal publishes no per-award ceiling.

## `sinceDays` (type: `integer`):

How far back to look for opportunities. On the first run this is your backfill; afterwards it just bounds the search, because only never-seen opportunities are emitted. EU forthcoming calls always pass this window - their opening date is in the future - so the first run also delivers the announced EU pipeline, capped by Max items.

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

Safety cap on opportunities emitted (and billed) in one run. Anything over the cap is NOT marked as seen, so it is emitted on the next run instead of being lost.

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

Named key-value store holding the seen-opportunity ids. Change it to run two independent watchlists (e.g. one per programme team) from the same actor.

## Actor input object example

```json
{
  "sources": [
    "grants-gov",
    "eu-funding-portal"
  ],
  "keywords": [],
  "categories": [],
  "fundingInstruments": [],
  "euStatuses": [
    "open",
    "forthcoming"
  ],
  "sinceDays": 14,
  "maxItems": 500,
  "stateStoreName": "grants-monitor-state"
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# 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": [],
    "categories": [],
    "fundingInstruments": [],
    "minAwardCeiling": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadeltas/grants-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": [],
    "categories": [],
    "fundingInstruments": [],
    "minAwardCeiling": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("datadeltas/grants-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": [],
  "categories": [],
  "fundingInstruments": [],
  "minAwardCeiling": 0
}' |
apify call datadeltas/grants-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datadeltas/grants-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/yvWWydfofeCf6nzcZ/builds/goJeXWZBKS8oBtspi/openapi.json
