# News Mentions API: Brand & Keyword Monitoring (`subimpact/news-mentions-api`) Actor

Search a curated index of publisher news feeds by keyword and get clean article rows: title, snippet, publisher URL, source, region, topic, language and publish time. Built for brand/PR monitoring, competitor tracking and market news in Malaysia, Southeast Asia and worldwide. Pay only per article.

- **URL**: https://apify.com/subimpact/news-mentions-api.md
- **Developed by:** [subimpact](https://apify.com/subimpact) (community)
- **Categories:** News, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 articles

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Mentions API — Brand & Keyword News Monitoring (MY, SEA, Global)

Search a curated index of **publisher news feeds** by keyword and get clean rows back: headline, snippet, the
publisher's own article link, source, region, topic, language and publish time. No browser, no proxy, no scraping
per page — one API call per page of results, JSON/CSV/Excel export, pay only for the articles you get.

Built for Malaysia and Southeast Asia first: the index crawls national dailies, business, tech, auto, finance,
energy and e-commerce press from MY, SG, ID, PH, TH and VN, plus a global layer of wires and international outlets.

### What you get

- **One row per article** (not per keyword): `matchedKeywords` lists every query that hit, so a story about
  "Petronas EV chargers" appears once with both keywords attached.
- **Publisher URLs**, not aggregator redirects — the link goes straight to thestar.com.my, paultan.org,
  asia.nikkei.com, etc.
- **Filter at query time**: region, topic, language, include/exclude publisher domains, date range or lookback.
- **Delta mode (`onlyNew`)** for scheduled monitoring: each run returns only the articles added since the previous
  run, with a per-query cursor stored in your own key-value store.

### Who it is for

- **Brand / PR / comms teams** tracking mentions of a company, product or executive.
- **Competitor and market intelligence** — what the local press says about rivals, launches, pricing, regulation.
- **Agencies and data teams** feeding a dashboard, alerting pipeline or LLM/RAG store with fresh, linkable news.
- **Analysts** who want SEA coverage without building and maintaining a feed crawler.

### Coverage

| Regions | Topics |
|---|---|
| `MY` Malaysia, `SG` Singapore, `ID` Indonesia, `PH` Philippines, `TH` Thailand, `VN` Vietnam, `GLOBAL` international | `general`, `business`, `tech`, `startups`, `auto`, `finance`, `crypto`, `marketing`, `ecommerce`, `energy` |

Coverage is a **curated list of publisher feeds** (RSS/Atom published by the outlet itself). It is not "all of the
web" and it does not include paywalled full text. Missing an outlet you need? Open an Issue on this actor and we add
it to the source list — that is how coverage grows.

Up to **180 days of history** is kept. Leave `regions`/`topics` empty to search everything.

### Query syntax

Each entry in `keywords` is a query:

| Syntax | Meaning | Example |
|---|---|---|
| `word word` | AND — all words must appear (title or snippet) | `Petronas profit` |
| `"exact phrase"` | phrase match | `"electric vehicle" Malaysia` |
| `a OR b` | either term (`OR` must be uppercase) | `Grab OR foodpanda` |
| `-term` / `-"phrase"` | exclude | `Petronas -sponsored` |

Matching is case-insensitive on the headline and the feed summary. `excludeKeywords` are appended to **every**
query, so you can strip recurring noise once: `["press release", "advertorial"]` adds
`-"press release" -advertorial` to each query (multi-word terms are quoted automatically).

Examples:

```json
{
  "keywords": ["Petronas", "\"electric vehicle\" Malaysia", "Grab OR foodpanda -promo"],
  "regions": ["MY", "SG"],
  "topics": ["business", "auto"],
  "excludeKeywords": ["advertorial"],
  "lookbackDays": 30
}
```

A query that consists only of exclusions is rejected — always keep at least one positive term.

### Scheduled monitoring (onlyNew)

1. Create a **Task** from this actor with your keywords and filters, and tick **Only new articles since the last run**.
2. Schedule the task (e.g. every hour) with an Apify **Schedule**.
3. First run: returns the lookback window (`lookbackDays`, default 7) and stores the index position it reached.
4. Later runs: return only articles ingested after that position, oldest first, and store the new position.

The cursor lives in the named key-value store **`news-mentions-state`** in your account, under a short hash of your
query (keywords + filters, excluding limits). Change a filter and you get a new cursor (a fresh full lookback); change
only `maxItemsPerKeyword` or dedupe and the cursor keeps working. Set `stateKey` to name the cursor yourself (useful
when one task tracks several brands).

Notes:

- The cursor only advances when the whole run finished cleanly. If a run stops early (charge limit, a keyword error),
  the cursor stays put and the next run repeats those articles rather than silently losing them.
- Do not mix delta runs and full runs in the same task.
- If a task was paused for a long time, its first run back returns everything since the last cursor (oldest first),
  capped by `maxItemsPerKeyword` per keyword.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | array of strings | — (required) | 1–100 queries in the syntax above |
| `excludeKeywords` | array of strings | `[]` | Appended to every query as `-term` (quoted when multi-word) |
| `regions` | array, enum | `[]` (all) | `MY, SG, ID, PH, TH, VN, GLOBAL` |
| `topics` | array, enum | `[]` (all) | `general, business, tech, startups, auto, finance, crypto, marketing, ecommerce, energy` |
| `languages` | array of strings | `[]` (all) | Feed language codes, e.g. `en`, `ms`, `id` |
| `domains` | array of strings | `[]` | Only these publishers (`thestar.com.my` also matches subdomains) |
| `excludeDomains` | array of strings | `[]` | Drop these publishers |
| `lookbackDays` | integer 1–180 | `7` | Days back from now; ignored when `dateFrom` is set |
| `dateFrom` / `dateTo` | ISO date | — | Override the lookback; a bare date covers the whole day, `dateTo` inclusive |
| `maxItemsPerKeyword` | integer 1–5000 | `100` | Cap per keyword; you pay only for returned articles |
| `dedupeAcrossKeywords` | boolean | `true` | One row per article with all matching keywords (off: one row per keyword match) |
| `onlyNew` | boolean | `false` | Delta mode (see above) |
| `stateKey` | string | — | Name of the delta cursor when `onlyNew` is on |

### Output

One dataset row per article:

```json
{
  "keyword": "Petronas",
  "matchedKeywords": ["Petronas", "\"electric vehicle\" Malaysia"],
  "title": "Malaysia electric vehicle sales jump as Petronas adds chargers",
  "snippet": "Electric vehicle registrations in Malaysia doubled while Petronas opened 40 new charging bays.",
  "url": "https://paultan.org/2026/10/23/malaysia-ev-sales-jump/",
  "sourceName": "Paul Tan's Automotive News",
  "sourceDomain": "paultan.org",
  "region": "MY",
  "topic": "auto",
  "language": "en",
  "publishedAt": "2026-10-22T23:30:00Z",
  "firstSeenAt": "2026-10-22T23:41:07Z",
  "imageUrl": "https://paultan.org/img/ev-chargers.jpg",
  "author": null,
  "matchedIn": ["title", "snippet"],
  "articleId": "3f9a1c0b7d2e4a51"
}
```

Fields: `keyword` (first matching keyword), `matchedKeywords`, `title`, `snippet` (≤320 characters), `url`,
`sourceName`, `sourceDomain`, `region`, `topic`, `language`, `publishedAt`, `firstSeenAt`, `imageUrl`, `author`,
`matchedIn` (`title` and/or `snippet`), `articleId` (stable id, sha1 of the canonical URL).

### Pricing

**$1.00 per 1,000 articles** returned — one charge per pushed row, nothing else. Platform usage (compute/network) is
paid by you on top of the event price and is typically **under $0.05 per 1,000 articles** for this actor. Each run
also costs a $0.00005 start fee.

Zero results cost nothing. If the news index is unavailable, the run fails before pushing or charging anything.

### How this compares

Most "news scraper" actors wrap Google News search. That works, but the link Google returns is a redirect
(`news.google.com/rss/articles/...`) that has to be resolved separately — often by a second, paid call per article —
before you have the publisher URL. This actor returns the publisher URL directly, from the publisher's own feed.

| | news-mentions-api | Typical Google News scraper |
|---|---|---|
| Article link | publisher URL, ready to cite | Google redirect, needs extra resolving |
| Dedupe across keywords | yes, `matchedKeywords` | usually one row per keyword |
| History | up to 180 days back | whatever Google currently serves |
| Delta mode | cursor-based `onlyNew` | usually none |
| Source metadata | region, topic, language, publisher domain | mostly title + link |
| Price | $1.00 / 1,000 articles | varies by actor — check the listing |

Check the individual listings for their current prices; they change.

### Notes

- With `dedupeAcrossKeywords` on, rows are pushed after all keywords have been scanned, so `matchedKeywords` is
  complete for every article. Turn it off if you prefer rows streamed to the dataset per keyword match.
- With `dedupeAcrossKeywords` on, articles are written newest-published first.
- The actor calls our news index directly (no browser, no proxy), so a 512 MB run is plenty and per-row compute cost
  is negligible.

### Limits

- **Headline + snippet + link only.** We never return full article text; read the article on the publisher's site.
- Coverage is **our source list**, not the whole web. New outlets are added on request (Issues).
- `snippet` is truncated at 320 characters and `title` at 300 by the index.
- At most 100 queries per run, 5,000 articles per query, and 180 days of history.
- Matching is on the headline and feed summary, so a mention buried deep in an article body can be missed.
- Publisher feeds occasionally publish late or break their own feed; the status message reports the index freshness
  so you can tell when results may be behind.

### FAQ

**Do I need an API key?** No. The actor is preconfigured against our news index; you just run it.

**Can I get the full article text?** No. The index stores headline, feed summary and the link. Fetch the page from the
publisher if you need the body (respect their terms).

**Why is an article I know about missing?** Either the publisher is not in the source list, its feed published after
your window, or the term appears only in the body rather than the headline/summary. Try a broader query or a longer
lookback, and open an Issue if the outlet is missing.

**How fresh is the data?** The index polls publisher feeds continuously; the final status message reports the last
ingest time and lag. If the index reports `degraded`, results may be behind and the run says so.

**Can I run several brands in one task?** Yes — put them all in `keywords`, or use one task per brand with `onlyNew`
and a `stateKey` per brand.

**What happens when I hit my max charge per run?** The run stops cleanly, keeps the rows it already delivered, and the
status message says the limit was reached. Raise the max charge per run (or lower `maxItemsPerKeyword`) and run again.

# Actor input Schema

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

1–100 search queries. Defaults to a demo query when omitted. Query syntax: words are ANDed by default (petronas profit = both words); "exact phrase" in double quotes; OR between terms (uppercase); -term or -"phrase" excludes. Example: Petronas OR "Petronas Dagangan" -sponsored.

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

Appended to every query as -term, so you can filter noise once instead of in each keyword. Multi-word terms are quoted automatically: "press release" becomes -"press release".

## `regions` (type: `array`):

Publisher regions to search. Leave empty for all regions. MY = Malaysia, SG = Singapore, ID = Indonesia, PH = Philippines, TH = Thailand, VN = Vietnam, GLOBAL = international outlets.

## `topics` (type: `array`):

Publisher sections to search. Leave empty for all topics.

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

ISO language codes of the publisher feed, e.g. en, ms, id, th, vi. Leave empty for all languages.

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

Restrict results to these publisher domains (subdomains included): thestar.com.my also matches biz.thestar.com.my. Leave empty for any publisher.

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

Drop results from these publisher domains (subdomains included).

## `lookbackDays` (type: `integer`):

How many days back to search, counted from now (1–180). The index keeps up to 180 days of history. Ignored when dateFrom is set, and on scheduled delta runs (onlyNew) after the first run.

## `dateFrom` (type: `string`):

Only articles published on/after this date. ISO format: 2026-10-01 or 2026-10-01T09:00:00Z.

## `dateTo` (type: `string`):

Only articles published on/before this date (inclusive; a bare date covers the whole day). ISO format: 2026-10-24 or 2026-10-24T18:00:00Z.

## `maxItemsPerKeyword` (type: `integer`):

Upper limit of articles fetched for each keyword (1–5000). You are charged only for the articles actually returned.

## `dedupeAcrossKeywords` (type: `boolean`):

On (default): an article matching several keywords appears once, with every matching keyword in matchedKeywords. Off: one row per keyword match, pushed as soon as it is fetched.

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

For scheduled monitoring: remembers how far the index it has been read (in the named key-value store news-mentions-state) and returns only articles added since. The first run returns the lookback window. Do not mix delta and full runs in the same task.

## `stateKey` (type: `string`):

Only for onlyNew. Name of the stored cursor. Leave empty to derive it from the query (keywords + filters), so different searches keep separate cursors in the same task.

## Actor input object example

```json
{
  "keywords": [
    "Petronas",
    "\"electric vehicle\" Malaysia",
    "Grab OR foodpanda -promo"
  ],
  "excludeKeywords": [],
  "regions": [
    "MY"
  ],
  "topics": [],
  "languages": [],
  "domains": [],
  "excludeDomains": [],
  "lookbackDays": 7,
  "maxItemsPerKeyword": 100,
  "dedupeAcrossKeywords": true,
  "onlyNew": false
}
```

# Actor output Schema

## `keyword` (type: `string`):

The first input keyword that matched this article

## `matchedKeywords` (type: `string`):

Every input keyword that matched this article (dedupeAcrossKeywords on)

## `title` (type: `string`):

Article headline from the publisher feed

## `snippet` (type: `string`):

Plain-text summary from the feed, max 320 chars (never full text)

## `url` (type: `string`):

Publisher article link

## `sourceName` (type: `string`):

Publisher name, e.g. The Star

## `sourceDomain` (type: `string`):

Registrable publisher domain, e.g. thestar.com.my

## `region` (type: `string`):

MY, SG, ID, PH, TH, VN or GLOBAL

## `topic` (type: `string`):

Publisher section (business, tech, auto, ...)

## `language` (type: `string`):

Feed language code, e.g. en, ms

## `publishedAt` (type: `string`):

Publication time, ISO 8601 UTC

## `firstSeenAt` (type: `string`):

When the index first ingested the article

## `imageUrl` (type: `string`):

Lead image URL, when present

## `author` (type: `string`):

Byline, when present

## `matchedIn` (type: `string`):

Fields that contained a query term: title, snippet

## `articleId` (type: `string`):

Stable article id (16 hex chars)

# 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": [
        "Petronas",
        "\"electric vehicle\" Malaysia"
    ],
    "regions": [
        "MY"
    ],
    "lookbackDays": 7,
    "maxItemsPerKeyword": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/news-mentions-api").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": [
        "Petronas",
        "\"electric vehicle\" Malaysia",
    ],
    "regions": ["MY"],
    "lookbackDays": 7,
    "maxItemsPerKeyword": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("subimpact/news-mentions-api").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": [
    "Petronas",
    "\\"electric vehicle\\" Malaysia"
  ],
  "regions": [
    "MY"
  ],
  "lookbackDays": 7,
  "maxItemsPerKeyword": 100
}' |
apify call subimpact/news-mentions-api --silent --output-dataset

```

## MCP server setup

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

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/21hmiWJoTJCa4xMrj/builds/vrZFjzNpvhaddjpjK/openapi.json
