# News & RSS Keyword Monitor (GDELT + Feeds) (`prevailing_glow/news-rss-keyword-monitor`) Actor

Monitor keywords in worldwide news (GDELT) and any RSS/Atom feed. Exclude words, language and country filters, dedupe, and only new articles on schedules.

- **URL**: https://apify.com/prevailing_glow/news-rss-keyword-monitor.md
- **Developed by:** [Dave West](https://apify.com/prevailing_glow) (community)
- **Categories:** News, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 new articles

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?

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

## News & RSS Keyword Monitor (GDELT + Feeds)

**A news monitor and RSS keyword tracker for brand mentions, competitors and topics.** Give it keywords (with
optional exclude words, language and country filters) and, if you like, your own RSS/Atom feeds. It searches
**worldwide online news through the free GDELT news index** and your feeds, removes duplicates, and returns **one row
per article**: title, link, source, publish date, language, matched keywords and a short snippet.

Schedule it and every run returns **only the articles you have not seen yet**, so it works as a lightweight
**media monitoring** tool and a **Google News alternative** for alerts in Slack, email, Google Sheets, Zapier, Make or
n8n. You pay **$0.001 per new article** ($1 per 1,000). Articles you have already received, duplicates and empty runs
add no article charges.

- **Global news coverage through GDELT**: the GDELT DOC 2.0 API covers online news in about 65 languages, updated
  every 15 minutes, with no API key needed. Search in English and you find coverage in every language (GDELT searches
  English machine translations of the full text).
- **Any RSS or Atom feed**: news sites, blogs, trade press, press-release feeds, YouTube channel feeds, and Google News
  RSS search URLs you copy yourself. Web page URLs work too if the page links to its feed.
- **Precise keyword matching**: case- and accent-insensitive, whole words and phrases ("AI" does not match "said"),
  optional prefix wildcard (`klimaat*`), any or all keywords, plus **exclude words**.
- **Language and country filters**: `en`, `nl`, `de`, ... and publisher countries (`US`, `GB`, `NL`, ...).
- **Deduplication**: the same story from GDELT and a feed, with tracking parameters, `www.`/`m.`/AMP variants or a
  " - Publisher" title suffix, is returned once (`alsoFoundIn` lists the other sources).
- **Monitoring mode**: delivered articles are remembered in a named key-value store in your account. Scheduled
  runs return only new articles (or everything, flagged with `isNew`).
- **Robust**: if GDELT is rate-limited or down, the run still delivers your feed articles. A broken feed never stops
  the run.
- **Lightweight**: HTTP only, no browser, no full-article scraping. Runs take seconds and cost almost nothing in
  platform usage.

### Use cases

- **Brand mentions and reputation**: your company, product or CEO in the news, excluding job ads or stock tips.
- **Competitor and market monitoring**: product launches, funding rounds, acquisitions, regulation.
- **PR and media monitoring**: coverage of a campaign or press release across countries and languages.
- **Topic alerts**: a daily feed of "artificial intelligence" + "regulation" news for a newsletter or research.
- **Feed aggregation with filters**: watch 50 trade-press RSS feeds and keep only the items that mention your terms.

### How it works

1. Enter your **keywords** (and optional exclude words, languages, countries).
2. Optionally add **RSS/Atom feed URLs**.
3. Run it and check the **Overview** table.
4. **Schedule it** (hourly or daily). Each run returns only new articles. Connect an integration to get alerts.

```
keywords ──► GDELT DOC 2.0 API (full-text news search) ──┐
                                                          ├─► filter ─► dedupe ─► new only? ─► dataset rows
RSS / Atom feeds ──► title + summary keyword match ───────┘                (seen IDs in a named KV store)
```

### Input

| Field | What it does | Default |
|---|---|---|
| `keywords` | Words or phrases to monitor. GDELT searches the full article text; feed items must mention a keyword in the title or summary. Trailing `*` = prefix wildcard (feeds only). | - |
| `gdeltKeywords` | Optional English search terms for GDELT. GDELT searches English machine translations of all articles, so use English terms here for Dutch, German, ... news and keep native keywords in `keywords` for your feeds. Empty = `keywords`. | - |
| `excludeKeywords` | Drop articles that mention any of these | - |
| `matchMode` | `any` keyword or `all` keywords | `any` |
| `feeds` | RSS / Atom feed URLs (Google News RSS search URLs and pages that link to a feed also work). With no keywords, every item from the feeds is returned. | - |
| `useGdelt` | Search GDELT as well as your feeds | `true` |
| `languages` | ISO 639-1 codes (`en`, `nl`, `de`, ...) or English names (`Dutch`). Applied to GDELT and to feeds that declare a language. | all |
| `countries` | Publisher countries for GDELT: ISO codes (`US`, `GB`, `NL`) or English names | all |
| `lookbackHours` | Only articles published within this window (GDELT: up to 2160 h = 3 months). 48 h by default because GDELT's index can lag. | 48 |
| `maxArticles` | Max rows per run, newest first. Articles over the cap stay unseen for the next run. | 200 |
| `onlyNew` | Return only articles not delivered before (monitoring mode) | `true` |
| `stateStoreName` | Named key-value store that remembers delivered articles. **Use one name per monitor.** | `news-rss-keyword-monitor-state` |
| `gdeltMaxRecords` | Articles requested from GDELT (max 250) | 250 |
| `gdeltMaxWaitSecs` | Total time for GDELT including retries before continuing with feeds only | 40 |
| `requestTimeoutSecs` | Timeout per request | 30 |
| `proxyConfiguration` | Optional proxy for GDELT requests only (helps if your runs keep getting rate-limited) | off |

#### Example input: brand monitoring

```json
{
  "keywords": ["Acme Robotics", "AcmeBot"],
  "excludeKeywords": ["vacancy", "job opening"],
  "languages": ["en", "de"],
  "feeds": [
    "https://techcrunch.com/feed/",
    "https://news.google.com/rss/search?q=%22Acme%20Robotics%22&hl=en-US&gl=US&ceid=US:en"
  ],
  "lookbackHours": 24,
  "onlyNew": true,
  "stateStoreName": "acme-brand-monitor"
}
```

#### Example input: Dutch news about housing and the cabinet

Native keywords for the Dutch feed, English terms for GDELT, and a language filter so GDELT returns Dutch sources only:

```json
{
  "keywords": ["kabinet", "woningmarkt"],
  "gdeltKeywords": ["housing market", "Dutch cabinet"],
  "languages": ["nl"],
  "countries": ["NL"],
  "feeds": ["https://feeds.nos.nl/nosnieuwsalgemeen"],
  "stateStoreName": "nl-politics-monitor"
}
```

### Output

One dataset row per article. The **Overview** view shows date, title, link, source, keywords, language, snippet,
where it was found and whether it is new.

```json
{
  "title": "OpenAI safety employee resigns, claiming the company's 'culture is broken'",
  "url": "https://techcrunch.com/2026/10/03/openai-safety-employee-resigns-claiming-the-companys-culture-is-broken/",
  "sourceDomain": "techcrunch.com",
  "publishedAt": "2026-10-03T16:30:01.000Z",
  "language": "en",
  "matchedKeywords": ["AI"],
  "snippet": "By his own admission, David Robinson is \"something of a cliché\": an employee at a leading AI company who issues a dire warning while resigning from their job.",
  "source": "https://techcrunch.com/category/artificial-intelligence/feed/",
  "isNew": true,
  "sourceName": "AI News & Artificial Intelligence | TechCrunch",
  "sourceCountry": null,
  "imageUrl": null,
  "matchedIn": "title/snippet",
  "alsoFoundIn": [],
  "articleId": "f143fce070e10faa",
  "collectedAt": "2026-10-03T20:08:56.736Z"
}
```

| Field | Meaning |
|---|---|
| `title`, `url` | Article headline and link (as published; Google News RSS items keep their news.google.com link) |
| `sourceDomain` | Publisher domain (`bbc.co.uk`, `nos.nl`) |
| `publishedAt` | Publish time in ISO 8601 UTC (GDELT: the time GDELT first saw the article) |
| `language` | ISO 639-1 language code when known |
| `matchedKeywords` | Your keywords found in the title/snippet. For GDELT matches in the article body, the searched keyword(s) when unambiguous (see `matchedIn`) |
| `snippet` | Short summary from the feed (max 300 characters). Always `null` for GDELT, which provides no summary. |
| `source` | `gdelt` or the feed URL the article came from |
| `isNew` | `true` if this article was never delivered by this monitor before |
| `sourceCountry`, `imageUrl`, `sourceName` | Extra metadata when available |
| `matchedIn` | `title`, `title/snippet` or `article text (GDELT)` |
| `alsoFoundIn` | Other sources that had the same story (merged duplicates) |
| `articleId`, `collectedAt` | Stable ID (from the canonical URL) and collection time |

A **`SUMMARY`** record in the run's key-value store shows the counts (found, filtered, duplicates, already seen,
delivered, charged), the exact GDELT query and status (requests, retries, errors), the status of each feed, and all
warnings.

### Scheduling (monitoring)

1. Save your input as a **task** (one task per brand or topic) and give each task its own `stateStoreName`.
2. Console -> **Schedules** -> create a schedule (for example every hour, or daily at 07:00) and add the task.
3. Add an **integration** to the task: Slack, email, Google Sheets, Zapier, Make, n8n or a webhook on "Run
   succeeded" to receive the new rows.

How "new" works: after an article is delivered, its canonical-URL ID and normalized-title ID are stored in the named
key-value store. The next run skips those, so the same story under a different URL (for example a Google News link
and the publisher link) is not reported twice. Articles cut off by `maxArticles` or by your max charge are **not**
marked as seen, so a later run delivers them. Seen IDs are kept for at least 30 days. To start over, delete the store
or use a new `stateStoreName`. Set `onlyNew` to `false` to get every match, with `isNew` on each row.

Tip: keep `lookbackHours` well above your schedule interval (the default 48 h suits hourly and daily runs). GDELT
sometimes publishes articles late, and the seen-store makes sure you never get duplicates.

### Pricing

**Pay per event: $0.001 per new article delivered** (`article` event, $1.00 per 1,000 new articles).

| You get | Price |
|---|---|
| A new article row (`isNew: true`) | $0.001 |
| Articles you already received (re-delivered with `onlyNew: false`) | free |
| Duplicates, filtered or excluded articles, failed feeds, GDELT outages | free |
| A scheduled run that finds nothing new | no article charges |

Examples: a daily brand monitor that finds 20 new articles costs about $0.02 a day (~$0.60 a month). A topic feed with
500 new articles a day costs about $0.50 a day. The Actor checks your **maximum cost per run** before delivering
each batch and stops cleanly when it is reached. Undelivered articles stay unseen and come in the next run.

### Limitations

- **GDELT rate limits and outages.** GDELT allows about one request every 5 seconds per IP address and is often slow
  (10-20 s per response). Shared IPs can get HTTP 429. The Actor retries with backoff for up to `gdeltMaxWaitSecs`
  (default 40 s) and then continues with feed articles only. The run's SUMMARY and log show it. Raise
  `gdeltMaxWaitSecs` or set a proxy if GDELT matters more than speed.
- **GDELT returns at most 250 articles per run** (newest first) and covers the last 3 months. It normally updates
  every 15 minutes, but its index sometimes lags. During testing on 2026-10-03, the newest GDELT article was about
  34 hours old, so a 24 h window returned nothing from GDELT. The default `lookbackHours` is therefore 48. The
  SUMMARY shows `gdelt.newestSeenAt`.
- **GDELT relevance**: GDELT matches the keyword anywhere in the (translated) article text, including sidebars and
  related-article links, so some GDELT rows are only loosely related. Feed rows always mention the keyword in the
  title or summary.
- **GDELT searches English translations.** GDELT machine-translates non-English news into English and searches
  only the translation (it dropped native-language search). Dutch keywords like `kabinet` find nothing there. Put
  English terms in `gdeltKeywords` and use `languages` to keep Dutch sources. Titles stay in the original language.
  When GDELT returns zero articles, the run log says so.
- **GDELT keywords**: keywords shorter than 3 characters (like "AI") and wildcards are not supported by GDELT. They are
  still used for feeds and titles, and the log says so. The country filter applies to GDELT only.
- **Only title, snippet and link are stored.** The Actor never downloads or stores full article bodies and does not
  extract emails or personal data. Feed items are matched on their title and summary only. GDELT matches the full text
  on its side and gives no snippet.
- **No Google scraping.** Google News works only through RSS search URLs you paste as a feed. Their links point to
  news.google.com redirects.
- **Paywalls**: headlines and links from paywalled sites appear, but the content behind the paywall is not accessed.
- **Deduplication** uses canonical URLs and normalized titles. Rewritten versions of a story with different
  headlines count as separate articles.
- Feed language comes from the feed's `<language>` tag. Feeds without one are not removed by the language filter.

### FAQ

**Do I need a GDELT or Google API key?** No. GDELT is free and public, and feeds are public.

**Can I monitor only RSS feeds?** Yes: add feeds and turn off `useGdelt`. Without keywords, every new feed item is
returned. That makes it an RSS-to-dataset monitor.

**Why does a GDELT row have empty `matchedKeywords`?** GDELT matched one of several keywords somewhere in the article
text, not in the title, and does not say which. `matchedIn` is then `article text (GDELT)`.

**How do I get alerts?** Schedule a task and add a Slack, email or webhook integration for successful runs. Only new
articles are in each run's dataset.

#### Quiet runs

When a scheduled run finds nothing new, the dataset gets one free row with `type: "status"` and `status: "no_new_articles"`, so you can see that the run happened. Set **Status row when nothing is new** (`statusRowWhenEmpty`) to `false` if you only want article rows.

# Actor input Schema

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

Words or phrases to look for (case- and accent-insensitive, whole words: "AI" does not match "said"). GDELT searches the full article text; feed articles must mention a keyword in the title or summary. A trailing <code>\*</code> is a prefix wildcard in feeds (GDELT searches the word itself). GDELT ignores keywords shorter than 3 characters; they still apply to feeds.

## `gdeltKeywords` (type: `array`):

GDELT searches English machine translations of all articles, including Dutch, German or French news. For non-English monitoring, enter the English terms here (e.g. <code>housing market</code>, <code>Dutch cabinet</code>) and keep your native-language keywords above for the feeds. Empty = GDELT uses the keywords above.

## `excludeKeywords` (type: `array`):

Articles mentioning any of these (in GDELT: anywhere in the text; in feeds: title or summary) are dropped.

## `matchMode` (type: `string`):

Return articles that mention any keyword, or only those that mention all of them.

## `feeds` (type: `array`):

Optional feeds to watch next to GDELT: RSS 2.0, RSS 1.0 (RDF) or Atom. A Google News RSS search URL you copied also works. A web page URL works if the page links to its feed. Leave keywords empty to get every article from these feeds.

## `useGdelt` (type: `boolean`):

Search the keywords in the GDELT DOC 2.0 news index (worldwide online news, about 65 languages, updated every 15 minutes). If GDELT is slow or down, the run still returns your feed articles.

## `languages` (type: `array`):

ISO 639-1 codes such as <code>en</code>, <code>nl</code>, <code>de</code>. Applied to GDELT and to feeds that declare a language. Empty = all languages.

## `countries` (type: `array`):

ISO country codes such as <code>US</code>, <code>GB</code>, <code>NL</code> (or English names). Filters GDELT by the publisher's country. Feeds are not filtered by country.

## `lookbackHours` (type: `integer`):

Only articles published within this window. Default 48 h because GDELT's index sometimes lags by a day or more; on scheduled runs the seen-store prevents duplicates. GDELT covers up to the last 3 months (2160 h).

## `maxArticles` (type: `integer`):

Cap on rows delivered per run (newest first). Articles over the cap are not marked as seen, so the next run can still deliver them.

## `onlyNew` (type: `boolean`):

Remember delivered articles between runs and return only new ones: ideal for schedules. Turn off to return every match (each row still shows isNew).

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

Named key-value store that remembers delivered articles. Use a different name per monitor (task) so they do not share state. Letters, digits and hyphens.

## `gdeltMaxRecords` (type: `integer`):

Articles requested from GDELT per run (GDELT's maximum is 250, newest first).

## `gdeltMaxWaitSecs` (type: `integer`):

Total time for GDELT including retries when it rate-limits (HTTP 429) or errors. After that the run continues with feed articles only. Raise it for unattended scheduled runs where a slower run is fine.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each GDELT or feed request.

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

Optional. GDELT allows one request every 5 seconds per IP address; a proxy helps if your runs keep getting rate-limited. Feeds are always fetched directly.

## `statusRowWhenEmpty` (type: `boolean`):

When a run finds no new articles, write one free row with type "status" so you can see the run happened. Turn off to keep the dataset strictly to articles.

## Actor input object example

```json
{
  "keywords": [
    "artificial intelligence",
    "AI"
  ],
  "matchMode": "any",
  "feeds": [
    "https://feeds.bbci.co.uk/news/technology/rss.xml",
    "https://techcrunch.com/category/artificial-intelligence/feed/"
  ],
  "useGdelt": true,
  "lookbackHours": 48,
  "maxArticles": 50,
  "onlyNew": true,
  "stateStoreName": "news-rss-keyword-monitor-state",
  "gdeltMaxRecords": 250,
  "gdeltMaxWaitSecs": 40,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "statusRowWhenEmpty": true
}
```

# Actor output Schema

## `articles` (type: `string`):

One row per matching article: title, link, source, published date, language, matched keywords, snippet and whether it is new.

## `summary` (type: `string`):

Counts per source (GDELT and each feed), new vs already-seen articles, charges and warnings.

# 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": [
        "artificial intelligence",
        "AI"
    ],
    "feeds": [
        "https://feeds.bbci.co.uk/news/technology/rss.xml",
        "https://techcrunch.com/category/artificial-intelligence/feed/"
    ],
    "lookbackHours": 48,
    "maxArticles": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("prevailing_glow/news-rss-keyword-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": [
        "artificial intelligence",
        "AI",
    ],
    "feeds": [
        "https://feeds.bbci.co.uk/news/technology/rss.xml",
        "https://techcrunch.com/category/artificial-intelligence/feed/",
    ],
    "lookbackHours": 48,
    "maxArticles": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("prevailing_glow/news-rss-keyword-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": [
    "artificial intelligence",
    "AI"
  ],
  "feeds": [
    "https://feeds.bbci.co.uk/news/technology/rss.xml",
    "https://techcrunch.com/category/artificial-intelligence/feed/"
  ],
  "lookbackHours": 48,
  "maxArticles": 50
}' |
apify call prevailing_glow/news-rss-keyword-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,prevailing_glow/news-rss-keyword-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/24OECRKmsfKjSWVNx/builds/avEICLQcSUS6sQ6h8/openapi.json
