# Global News Monitor: Articles by Keyword, Tone and Topic (`jtpalms/global-news-monitor`) Actor

News and media monitoring in 65+ languages by keyword, topic, company or tone from GDELT open data. No proxies, no search engine scraping. One row per article with real publisher URL, publish time, tone, themes and organizations. Schedule with only-new for alerts. USD 1.50 per 1,000 articles.

- **URL**: https://apify.com/jtpalms/global-news-monitor.md
- **Developed by:** [JT Palms](https://apify.com/jtpalms) (community)
- **Categories:** News, Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 news articles

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

## Global News Monitor: Articles by Keyword, Tone and Topic

**News alerts from open data.** This actor reads the [GDELT Project](https://www.gdeltproject.org/)'s open 15-minute article files and returns one row per matching article: the real publisher URL, the headline, the publish time, GDELT's tone score, its themes and the organizations it found. No search engine is scraped, no proxy is used and no API key is needed. Schedule it with **Only new articles** and it becomes an alert feed that charges only for news you have not seen.

USD 1.50 per 1,000 articles. A run that finds nothing new costs only the start fee.

### What people use it for

- **Brand and competitor watch.** Keywords `acme corp`, `acme*`, or organization `acme corp`; one scheduled task per brand or client, hourly or daily.
- **Issue monitoring for PR and comms teams.** `tariffs`, `data breach`, `strike` with **Maximum tone** `-3` to see only clearly negative coverage.
- **Sales and BD triggers.** Trigger tags `funding`, `acquisition`, `leadership-change` or `layoffs` on the companies you sell to.
- **Research corpus.** Every article in a time window (empty keywords), with tone, GDELT theme codes and countries, reproducible from the same public files.
- **AI agent tool.** "What is in the news about X in the last 6 hours", answered with real article URLs, not redirect links.

### Sample output

Real rows from a run on 2026-10-06 (keywords `layoffs`, `acquire`, `ransomware`, `interest rates`; 6 hours; English):

| Published (UTC) | Title | Publisher | Tone | Trigger tags | Countries | Also on |
|---|---|---|---|---|---|---|
| 2026-10-06 18:30 | C.H. Robinson agrees to acquire RXO in $5.8 billion cash-and-stock deal | logisticsmgmt.com | 7.56 | acquisition | US | |
| 2026-10-06 18:30 | Stelco layoffs: Feds give U.S. owner 5 days to explain planned layoffs | cp24.com | -0.57 | layoffs | CA | 1 other site |
| 2026-10-06 16:31 | Maine regulators deny CMP's $7 rate hike as utility weighs potential layoffs | pressherald.com | -1.03 | layoffs, regulatory | US | 2 other sites |
| 2026-10-06 14:51 | Uber to Acquire EzCater for $2.3B, Expanding Catering Reach | businessinsider.com | 2.6 | acquisition | | |
| 2026-10-06 10:30 | Shionogi to acquire IntraBio in deal worth $2bn | pmlive.com | -0.59 | acquisition | JP, JO, US | |

One full row (JSON):

```json
{
  "id": "20261006183000-14",
  "url": "https://www.logisticsmgmt.com/article/c.h_robinson_agrees_to_acquire_rxo_in_5.8_billion_cash_and_stock_deal/lm_october_2026",
  "title": "C.H. Robinson agrees to acquire RXO in $5.8 billion cash-and-stock deal",
  "domain": "logisticsmgmt.com",
  "publishedAt": "2026-10-06T18:30:00.000Z",
  "seenAt": "2026-10-06T18:30:00.000Z",
  "language": "en",
  "tone": 7.56,
  "positive": 8,
  "negative": 0.44,
  "polarity": 8.44,
  "wordCount": 195,
  "themes": ["EPU_ECONOMY_HISTORIC", "TAX_ECON_PRICE", "TAX_FNCACT_CEO", "TAX_ETHNICITY_AMERICAN", "WB_135_TRANSPORT", "EPU_POLICY_REGULATORY", "LEADER", "TAX_FNCACT_PRESIDENT", "USPEC_POLITICS_GENERAL1", "TAX_FNCACT_CHAIRMAN"],
  "organizations": [],
  "countries": ["US"],
  "image": "https://www.logisticsmgmt.com/images/2026_article/ch-robinson-building3.jpg",
  "matchedKeywords": ["RXO"],
  "triggerTags": ["acquisition"],
  "alsoOnDomains": [],
  "source": "gdelt-gkg",
  "citation": "Contains data from the GDELT Project, https://www.gdeltproject.org/",
  "fetchedAt": "2026-10-06T19:19:34.494Z"
}
```

### How it works and how fresh it is

1. GDELT publishes one Global Knowledge Graph file every 15 minutes with every article it processed in that slot (about 1,500 English-language articles per file, about 145,000 a day). The actor builds the list of files for your window, newest first.
2. It downloads them (4 at a time, with retries), unzips them as a stream and keeps the articles that pass your filters. Once **Maximum articles** matches are in hand, it stops reading older files.
3. It merges duplicates: the same URL seen twice, and (with **Merge syndicated copies**) the same headline on many sites, keeping the earliest copy and naming the other publishers in `alsoOnDomains`.
4. Rows come out newest first. Each row is charged once.

Freshness: English-language sources appear within about 15 minutes of GDELT seeing them. Translated (non-English) sources arrive **1 to 3 hours later**, because GDELT translates them first; on 2026-10-06 the newest translation file was about 1 hour 15 minutes behind. A file that is not out yet is listed under `filesNotYetPublished` in the run's SUMMARY and picked up by a later run, never counted as an error.

`publishedAt` is the publisher's own timestamp when GDELT found one (about half of the articles), otherwise the time GDELT saw the article (`seenAt`). The window always refers to `seenAt`, so an older article first seen today is included.

### Filters

All filters combine with AND. Lists inside one filter combine with OR.

| Input | What it does |
|---|---|
| `keywords` | Match in the title, whole words, any case: `layoffs` never matches "MLB Playoffs". Several words match as a phrase (`interest rates`). `layoff*` matches word endings. `/regex/` is a regular expression. Empty = every article in the window. |
| `excludeKeywords` | Drop articles whose title contains any of these. |
| `matchIn` | `title` (default), `title_or_organizations`, or `title_or_themes` (theme codes are matched as words, so `layoffs` finds `ECON_LAYOFFS`). |
| `organizations` | GDELT's organization names, lower case: `federal reserve`, `european central bank`, `tesla`. |
| `themes` | GDELT theme code prefixes: `ECON_` (economy), `ECON_BANKRUPTCY`, `TAX_` (taxonomies), `WB_` (World Bank topics), `CYBER_ATTACK`. The full list: [LOOKUP-GKGTHEMES.TXT](https://data.gdeltproject.org/api/v2/guides/LOOKUP-GKGTHEMES.TXT). |
| `domains` / `excludeDomains` | Publisher allow and block lists. `reuters.com` also covers its subdomains. |
| `countries` | Two-letter ISO codes of places the story mentions (GDELT writes FIPS codes such as UK and GM; the actor converts them to GB and DE). |
| `languages` | `en` (default) reads the main files. Any other code (`fr`, `de`, `es`, `zh`, `ar`, `ru`...) also reads the translation files. Empty = all languages GDELT reads (English plus 65). |
| `minTone` / `maxTone` | GDELT's overall tone, roughly -10 to +10; most articles sit between -5 and +5. |
| `triggerTags` | Rule-based headline tags (below). Every row lists its tags even when you do not filter on them. |
| `lookbackHours` | Window ending now, 1 to 72 hours (default 6). |
| `fromDateTime` / `toDateTime` | Explicit UTC window, for example `2026-10-05T00:00:00Z` to `2026-10-05T12:00:00Z`. At most 72 hours per run. Files exist back to 2015-02-19. |

Non-English titles stay in their own language, so English keywords will not match them. GDELT's organizations and themes for those articles come from its English translation, so `matchIn: title_or_organizations`, `organizations` or `themes` do find them.

#### Trigger tags and their measured precision

Tags are plain keyword rules on the English headline, not a model. Precision below is the share of correct tags in 20 headlines per tag, read by hand, from 25,163 distinct English headlines GDELT published on 2026-10-06 between 13:00 and 19:00 UTC. Recall was not measured: the rules miss headlines worded in other ways.

| Tag | Headlines tagged in the sample window | Correct in 20 |
|---|---|---|
| `funding` | 27 | 16 (misses: charity drives, contract wins) |
| `acquisition` | 207 | 19 |
| `layoffs` | 22 | 18 |
| `leadership-change` | 58 | 19 |
| `lawsuit` | 183 | 17 (criminal "legal action", a person named Sue) |
| `bankruptcy` | 36 | 19 |
| `earnings` | 37 | 17 (market-wide earnings pieces) |
| `product-launch` | 119 | 14 (government systems, festival lineups) |
| `regulatory` | 60 | 19 |
| `partnership` | 83 | 15 (diplomatic "strategic partnerships") |
| `security-incident` | 60 | 17 (articles about preventing attacks) |

### Scheduling recipe: an alert feed

1. Save your filters as a task, with **Only new articles** on and **Look back** at 6 hours (the overlap catches late translated files).
2. Schedule the task hourly (or daily with **Look back** 24).
3. Add an Apify integration: Slack, email, a webhook or Zapier on "Run succeeded", or read the dataset from the API.

The first run outputs the current matches (or nothing, with **First run with Only new** = None). Later runs output only articles no earlier run output. The memory is kept per set of filters in your own `global-news-monitor-state` key-value store for 7 days (up to 200,000 articles), so two tasks with different keywords never hide each other's news. Matches cut off by **Maximum articles** come out in the next run while they are still inside the window.

### Input example

```json
{
  "keywords": ["acme corp", "acme*"],
  "excludeKeywords": ["acme anvils"],
  "triggerTags": ["funding", "acquisition", "leadership-change"],
  "languages": ["en"],
  "lookbackHours": 6,
  "onlyNew": true,
  "maxResults": 200
}
```

### Output fields

| Field | Meaning |
|---|---|
| `id` | GDELT record id (file stamp plus row number; `-T` marks a translated source). |
| `url` | The publisher's article URL, as GDELT recorded it. |
| `title` | Headline as published, HTML entities decoded (long dashes are written as a plain hyphen). |
| `domain` | Publisher domain. |
| `publishedAt` | Publisher's timestamp when known, else `seenAt` (UTC). |
| `seenAt` | Time of the GDELT file the article came from (UTC). |
| `language` | Source language (`en` for the main files, otherwise the original language). |
| `tone`, `positive`, `negative`, `polarity` | GDELT tone scores (machine-estimated from the article text). |
| `wordCount` | Article length GDELT measured. |
| `themes` | Top 10 GDELT theme codes by mentions. |
| `organizations` | Top 10 organizations by mentions (GDELT's lower-case names). |
| `countries` | ISO codes of countries the article mentions, by mentions (up to 10). |
| `image` | The page's sharing image, if any. |
| `matchedKeywords` | Which of your keywords matched. |
| `triggerTags` | Rule-based tags of the headline. |
| `alsoOnDomains` | Other publishers that ran the same headline (with **Merge syndicated copies**). |
| `text`, `textStatus` | Only with **Add article text**: the main text, and `ok` or why there is none (`robots-disallowed`, `paywalled`, `blocked`, `login-page`, `pdf`, `not-html`, `no-text-found`, `http-<code>`, `failed`). |
| `source`, `citation` | `gdelt-gkg` and the GDELT credit line. |
| `fetchedAt` | When this run wrote the row. |

The run's **SUMMARY** record lists the window, files read, files not yet published or missing, files skipped as damaged, articles scanned, matches, duplicates merged and what was charged.

### Article text (optional, off by default)

With **Add article text**, the actor fetches each output article's page once and adds its main text (up to 50,000 characters). It reads the site's robots.txt first and skips pages it disallows, sends at most one request per second to a site, waits at most 10 seconds, uses no proxy, and never works around a paywall: pages marked as not free, login pages, blocked pages and PDFs keep `text: null` and are not charged for text. In tests on 2026-10-06, 14 of 16 real article links returned text from a home connection and 5 of 10 from Apify's servers (the other 5: 2 marked paywalled, 2 disallowed by robots.txt, 1 blocked), so expect roughly half to two thirds of articles to come back with text. Article text is the publisher's work; use it for your own analysis and respect their terms.

### Pricing

Pay per event:

| Event | Price (FREE and BRONZE plans) |
|---|---|
| Run start | USD 0.002 |
| News article (one output row) | USD 0.0015, so USD 1.50 per 1,000 |
| Article text (only when text was returned) | USD 0.004, so USD 5.50 per 1,000 with text |

SILVER plans pay 10% less and GOLD and above 20% less per article. Examples:

- The default input (two keywords, 6 hours, up to 50 articles) returned 14 articles on 2026-10-06: about USD 0.023.
- A daily task with about 30 new matches a day: about USD 1.35 a month for articles plus USD 0.06 in starts.
- An hourly only-new task: 720 starts a month (USD 1.44) plus USD 0.0015 per new article.
- Set **Maximum cost per run** in the run options to cap any run; the actor stops cleanly at that limit and keeps what it saved.

### Attribution and data source

Contains data from the GDELT Project (https://www.gdeltproject.org/). GDELT states that its datasets "are available for unlimited and unrestricted use for any academic, commercial, or governmental use of any kind without fee", with a citation and a link to its website when redistributing. Every row carries that citation in `citation`; please keep it when you republish the data.

Person names, quotations and page authors are in GDELT's files but are left out on purpose: the actor never reads those columns.

### Limits and honest notes

- GDELT's coverage is broad but not complete, and its ranking and coverage differ from those of consumer news apps.
- Titles only, no snippet: GDELT does not publish article summaries. Use **Add article text** when you need the body.
- Tone, themes, organizations and countries are machine-extracted by GDELT and can be wrong.
- Trigger tags are rules with the precision shown above; check them before acting on a tag.
- Windows longer than 24 hours download hundreds of MB (about 150 MB per 6 hours of English files) and take longer; 72 hours is the maximum per run.
- If GDELT renames its files or adds limits, runs will report it in SUMMARY; the actor does not fall back to any other source.

### Polite fetching

Every request names this actor (`Mozilla/5.0 (compatible; global-news-monitor/1.0; +https://apify.com/JTPalms)`). GDELT files are static downloads from data.gdeltproject.org, at most 4 at a time, each downloaded once per run. No proxies, no browser automation, no header tricks.

### Related actors

- [Company News Triggers: Funding, Layoffs, M\&A and More](https://apify.com/JTPalms/company-news-triggers): Watch a list of companies for buying-signal news from open GDELT data: funding, acquisitions, layoffs, expansions, leadership changes,...
- [News Article Extractor: Text, Date, Image from URLs and Feeds](https://apify.com/JTPalms/news-article-extractor): News article scraper and extractor: turn article URLs, RSS or Atom feeds and news sitemaps into clean rows: title, Markdown and plain...
- [RSS Feed Reader & Scraper: Atom, JSON Feed & New-Item Monitor](https://apify.com/JTPalms/rss-feed-monitor): RSS feed reader and scraper.
- [Hacker News Scraper: Stories, Comments, Search & Alerts](https://apify.com/JTPalms/hacker-news): Hacker News front page, new, best, Ask HN, Show HN and job lists, or keyword search with date, points and comment filters.

# Actor input Schema

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

An article matches when its title contains any of these, as whole words in any case ("layoffs" never matches "playoffs"). Several words match as a phrase. End a word with \* to match endings (layoff\* matches layoffs), or write /regex/. Empty = every article in the window (use Maximum articles).

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

Drop articles whose title contains any of these (same matching rules).

## `matchIn` (type: `string`):

Title only, or also GDELT's organization names or theme codes. Non-English titles stay in their own language, but GDELT's organizations and themes come from its English translation, so the wider options also find foreign-language articles.

## `organizations` (type: `array`):

Keep only articles where GDELT found one of these organizations (lower-case names such as federal reserve, european central bank, tesla). Whole-word match.

## `themes` (type: `array`):

Keep only articles tagged with a GDELT theme code that starts with any of these, for example ECON\_ (economy), ECON_BANKRUPTCY, TAX\_, WB\_ (World Bank topics), CYBER_ATTACK. Full list: https://data.gdeltproject.org/api/v2/guides/LOOKUP-GKGTHEMES.TXT

## `domains` (type: `array`):

Publisher domains to keep (reuters.com also keeps www.reuters.com and other subdomains). Empty = all publishers.

## `excludeDomains` (type: `array`):

Publisher domains to drop.

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

Two-letter ISO country codes (US, GB, DE, CN, IN...). Keeps articles that mention a place in any of these countries. This is about the story's locations, not the publisher's country.

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

Two-letter codes of the source languages to keep. en reads GDELT's main files (English-language sources); any other code (fr, de, es, zh, ar, ru...) also reads GDELT's translation files, which arrive 1 to 3 hours late. Empty = all 65+ languages.

## `minTone` (type: `number`):

GDELT's overall tone of the article, roughly -10 (very negative) to +10 (very positive); most articles fall between -5 and +5. Keep articles at or above this value.

## `maxTone` (type: `number`):

Keep articles at or below this value, for example -3 for clearly negative coverage.

## `triggerTags` (type: `array`):

Keep only articles whose English headline matches any of these rule-based tags. Every row also lists its tags in the output. Rules, not a model: see the README for the measured precision.

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

Window ending now. GDELT publishes one file every 15 minutes, so 6 hours = 24 files (about 150 MB read). Longer windows take longer.

## `fromDateTime` (type: `string`):

Optional explicit window start, for example 2026-10-05T00:00:00Z (UTC). Overrides Look back. At most 72 hours per run; GDELT 2.0 files start on 2015-02-19.

## `toDateTime` (type: `string`):

Optional explicit window end (UTC). Empty = now.

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

Remember which articles earlier runs output and output only new ones. Schedule the actor hourly or daily to get news alerts; runs with nothing new output nothing and cost only the start. The memory is kept per set of filters in your global-news-monitor-state key-value store for 7 days.

## `firstRunOutput` (type: `string`):

All: the first run outputs the current matches. None: the first run outputs nothing and only records what is there, so alerts start with the next run.

## `dedupeSyndication` (type: `boolean`):

The same headline often runs on dozens of sites. Keep the earliest copy and list the other publishers in alsoOnDomains.

## `fullText` (type: `boolean`):

Fetch each article page and add its main text (extra charge per article that returned text). Respects robots.txt, one request per second per site, no proxy, no paywall bypass: blocked or paywalled pages keep text null and are not charged for text.

## `maxResults` (type: `integer`):

Hard cap per run. Files are read newest first and reading stops once the cap is reached.

## Actor input object example

```json
{
  "keywords": [
    "interest rates",
    "layoffs"
  ],
  "matchIn": "title",
  "languages": [
    "en"
  ],
  "lookbackHours": 6,
  "onlyNew": false,
  "firstRunOutput": "all",
  "dedupeSyndication": true,
  "fullText": false,
  "maxResults": 50
}
```

# Actor output Schema

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

One row per matching article in the default dataset (JSON, CSV, Excel via the format parameter).

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

Window, files read, files not yet published or missing, articles scanned and matched, duplicates merged, rows output and charged.

# 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": [
        "interest rates",
        "layoffs"
    ],
    "languages": [
        "en"
    ],
    "lookbackHours": 6,
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("jtpalms/global-news-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": [
        "interest rates",
        "layoffs",
    ],
    "languages": ["en"],
    "lookbackHours": 6,
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("jtpalms/global-news-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": [
    "interest rates",
    "layoffs"
  ],
  "languages": [
    "en"
  ],
  "lookbackHours": 6,
  "maxResults": 50
}' |
apify call jtpalms/global-news-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jtpalms/global-news-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/XQ4BLHKUBhi68zAKA/builds/G5xS5PfdgqDd21GIN/openapi.json
