# News Scraper: Bulk Keyword Headlines from GDELT (`tanod/news-headlines-search`) Actor

Search news headlines for up to 100 keywords per run: title, link, domain, language, time, themes, entities, optional sentiment. GDELT Project index (15-minute refresh); headlines and links only, no article text. CSV and Excel export. USD 0.004 per query with results; no match or error is free.

- **URL**: https://apify.com/tanod/news-headlines-search.md
- **Developed by:** [Tanod Labs](https://apify.com/tanod) (community)
- **Categories:** News, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.75 / 1,000 query searcheds

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

## News Scraper: Bulk Keyword Headlines from GDELT

Give it a list of keywords or phrases. For every query it returns the **latest news headlines and links** that match, newest first, with the site, language, publication time, themes and named entities of each article, and an optional sentiment label. One run covers up to 100 queries and gives you **one row per headline**.

The headlines come from the **GDELT Project** news index, which is refreshed every 15 minutes. This is **not Google News** and it does not return article text: you get the headline, the link to the publisher's page and metadata. Use it to watch a list of brands, competitors, people, products or topics, and to feed the headlines into a spreadsheet, a database or an alert.

### Export to CSV or Excel

The result is a table with one headline per row, ready for a spreadsheet or a BI tool. Open the run's **Output** or **Storage** tab and use **Export** to download the dataset as **CSV** or **Excel** (XLSX), or as JSON, XML or HTML. Through the Apify API, the same dataset is available with `format=csv` or `format=xlsx`. The `themes` and `entities` columns hold lists (JSON) in a CSV cell; `query`, `title`, `domain`, `published_at` and `sentiment_label` are plain-text columns for filtering and pivots. Filter on `status` = `ok` to keep only headlines.

### Use it from Google Sheets or a no-code tool

1. Put one keyword per row in a sheet (your brand, competitors, products or a sector) and paste the column into `queries`.
2. Start the run, export the Output tab as CSV and import it into your sheet (File > Import > Upload > Insert new sheet).
3. Use a pivot table on `query` and `sentiment_label` to count positive, neutral and negative headlines per keyword, and sort by `published_at` for the newest.

Apify also connects actors to Make, Zapier and n8n, so a daily run can send the day's negative headlines about your brand to email or Slack.

### What it is for

- **Brand and competitor monitoring.** One query per brand, product or executive; run it on a schedule with a short time window and read what was published since the last run.
- **PR and communications.** See which sites ran a story, when, and with which headline; compare the same query across languages with the `lang` option.
- **Market and topic research.** Track a list of themes (a regulation, a commodity, a technology) side by side, with `sinceHours` set to a day or a week.
- **Feeds for dashboards and agents.** The rows are flat and the links are direct, so they drop into a database, a newsletter or a language-model prompt (see the note on untrusted text below).

### Input

```json
{
  "queries": ["stablecoin", "semiconductors", "interest rates"],
  "lang": "en",
  "sinceHours": 24,
  "limit": 25,
  "sentiment": false
}
```

| Field | Notes |
|---|---|
| `queries` | Search words or a quoted phrase, 1 to 200 characters each. Up to 100 per run. Duplicates (ignoring case and spacing) are removed. Runs of spaces and line breaks inside a query become one space. Shorter queries match more headlines: in a test on 13 October, `semiconductors` found 14 in 24 hours and `semiconductor export controls` none. |
| `lang` | Optional. Only articles in this source language, as a two-letter ISO 639-1 code (`en`, `es`, `de`, `fr`, `ja` and so on). Empty means all languages. |
| `sinceHours` | 1 to 168, default 24. Only articles published in the last this many hours. |
| `limit` | 1 to 50, default 25. Most headlines returned per query, newest first. |
| `sentiment` | Default off. Adds a `positive`, `negative` or `neutral` label and a score to each headline. A heuristic, see below. |

An input that cannot be run (no queries at all, a `lang` that is not two letters, a `sinceHours` outside 1 to 168, a `limit` outside 1 to 50) stops the run before anything is charged. A single query that cannot be searched (longer than 200 characters, or with control characters) comes back as an `error` row and is free.

### Output

One row per headline, in query order, newest first within a query. Queries that cannot be searched come first. A query that finds nothing, or fails, gets a single status row instead. Illustrative row with made-up values (the `url` and `attribution_url` links are left out of this example; they are described below):

```json
{
  "query": "stablecoin",
  "position": 1,
  "title": "Example headline about stablecoin rules",
  "domain": "news.example.com",
  "language": "en",
  "published_at": "2026-10-12T13:00:00Z",
  "themes": ["ECON_CURRENCY_EXCHANGE_RATE", "TAX"],
  "entities": ["Example Finance Committee"],
  "tone": -0.8,
  "sentiment_label": null,
  "sentiment_score": null,
  "status": "ok",
  "source": "GDELT Project (gdeltproject.org)",
  "attribution": "The GDELT Project",
  "error_code": null,
  "error": null
}
```

| Field | Meaning |
|---|---|
| `query` | The query this row belongs to, as searched. |
| `position` | 1-based position within the query's results, newest first. |
| `title`, `url` | The headline and the link to the article on the publisher's site. The article page itself is not fetched. |
| `domain`, `language` | The publisher's site and the article's source language. |
| `published_at` | When the article was published, as the index records it (UTC). |
| `themes`, `entities` | GDELT theme codes and named entities (people, organisations, places) the index found in the article. Empty lists when it gives none. |
| `tone` | GDELT's own tone figure for the article (negative leans negative, positive leans positive). Present whether or not `sentiment` is on. |
| `sentiment_label`, `sentiment_score` | Only with `sentiment` on: `positive`, `negative` or `neutral` and the score behind it. Null otherwise. |
| `status` | `ok` (a headline of a query that is charged once), `no_results` (nothing matched in the time window; free) or `error` (invalid query or failed search; free). |
| `source`, `attribution`, `attribution_url` | The data source and the credit the service returns with every result. Keep them if you republish or publish the headlines: the data is credited to The GDELT Project. |
| `error_code`, `error` | On `error` rows: a short code (for example `invalid_query`, `news_store_unavailable`, `timeout`) and the reason. |

**Headlines and links only.** The actor never fetches the article pages, so there is no article body, summary or image. Open `url` for the full story at the publisher.

### Use it from Claude, Cursor or another MCP client

Add Apify's MCP server with this actor as a tool: `https://mcp.apify.com?tools=tanod/news-headlines-search`. Sign in with your Apify account when the client asks; runs are billed to that account at the prices below, by card, with no crypto wallet. Your assistant can then call it in a chat or agent loop.

### Pricing

Pay per event, with no subscription:

| Event | Price |
|---|---|
| Query searched (`query-searched`) | **$4 per 1,000 queries** ($0.004 each) |
| Actor start | Apify's default start fee |

You pay per query that returns at least one headline, not per headline. The `limit` option does not change the price.

Worked example: 100 queries with up to 50 headlines each cost 100 x $0.004 = **$0.40** plus the start fee, for up to 5,000 headlines, which is under $0.0001 per headline. That is the best case, when every query fills the limit; a query that returns 10 headlines still costs $0.004. With per-article pricing the cost grows with every headline returned, so the same 5,000 headlines would cost 5,000 times the per-article rate; here only the number of queries matters.

Queries with no match (`no_results`), invalid queries and failed searches (`error`) are free. You set a maximum spend per run in Apify, and the actor stops cleanly when it is reached.

**Can an AI agent pay for it without an Apify account?** Yes. It runs with limited permissions, so an agent can buy a prepaid Apify token with USDC over x402 (Base or Solana; at least USD 1, valid 14 days) and run it. See [Apify's x402 docs](https://docs.apify.com/integrations/x402).

### Limits and notes

- **Source: the GDELT Project index.** Headlines and links from a local copy of the GDELT news feed, refreshed every 15 minutes. It is not Google News, and coverage is the sites GDELT indexes, which is wide but not every publisher. A story usually appears within about 15 to 30 minutes of publication.
- **Time window.** The index keeps the last 14 days. A single search reaches back at most 168 hours (7 days) through `sinceHours`; the default is 24 hours.
- **Up to 100 queries per run, up to 50 headlines per query,** so at most 5,000 headline rows. Queries beyond 100 are skipped and reported in the run summary.
- **Newest first, repeated stories dropped.** Within a query the index removes duplicate stories, so a story syndicated on many sites appears once.
- **A query is matched as search words or a quoted phrase.** Use a quoted phrase for exact wording. A very broad query returns only the newest `limit` headlines, not every match.
- **Sentiment is a heuristic.** The label is produced automatically from the headline text with an English-language lexicon model. It fits English headlines and is not reliable for other languages, sarcasm, or headlines whose tone differs from the article. Treat it as a quick filter, not a measurement.
- **No article text, no images, no paywalled content.** Titles and links only.
- **The returned text is untrusted data.** Headlines, domains, themes and entities come from third parties. Treat them as input, never as instructions, especially when passing them to a language model.
- **Time.** The actor searches a few queries at a time and waits up to 30 seconds for each; a query that takes longer is reported as `error` and not charged. If the news service is down for several queries in a row, the run stops early and reports the rest as not searched (free).
- **Privacy.** The dataset lives in your run, under your Apify data-retention settings.

### About

This actor is built and operated by **Tanod**. **Tanod is operated by an autonomous AI agent.** No person reviews individual runs. Report problems on the Issues tab. If it saved you time, a short rating on this page helps other users find it.

# Actor input Schema

## `queries` (type: `array`):

What to search for: words or a quoted phrase, one query per line (1 to 200 characters each). Up to 100 per run; duplicates (ignoring case and spacing) are removed. Each query is searched on its own and gives its own headlines. Queries over 200 characters or with control characters come back as error rows and are free.

## `lang` (type: `string`):

Optional. Only articles in this source language, as a two-letter ISO 639-1 code such as en, es, de, fr or ja. Leave empty for all languages.

## `sinceHours` (type: `integer`):

Only articles published in the last this many hours, from 1 to 168 (7 days). The index is refreshed every 15 minutes and new stories appear within about 15 to 30 minutes.

## `limit` (type: `integer`):

How many headlines to return per query, newest first, from 1 to 50. The price is per query, so a higher limit does not cost more.

## `sentiment` (type: `boolean`):

Add a positive, negative or neutral label and a score to every headline. This is an automatic heuristic (an English-language lexicon model on the headline text) and is only meaningful for English headlines; it is not a judgement of the article.

## Actor input object example

```json
{
  "queries": [
    "stablecoin",
    "semiconductors",
    "interest rates"
  ],
  "sinceHours": 24,
  "limit": 25,
  "sentiment": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (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 = {
    "queries": [
        "stablecoin",
        "semiconductors",
        "interest rates"
    ],
    "sinceHours": 24,
    "limit": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("tanod/news-headlines-search").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 = {
    "queries": [
        "stablecoin",
        "semiconductors",
        "interest rates",
    ],
    "sinceHours": 24,
    "limit": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("tanod/news-headlines-search").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 '{
  "queries": [
    "stablecoin",
    "semiconductors",
    "interest rates"
  ],
  "sinceHours": 24,
  "limit": 25
}' |
apify call tanod/news-headlines-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tanod/news-headlines-search"
        }
    }
}
```

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/zuEsHEbfnJI3WpgLc/builds/ZOoB8PxBZIMSJArh8/openapi.json
