# News Delta API - New-Only Google News + RSS/Atom Monitor (`kaz_kakyo/news-delta-api`) Actor

Monitor Google News keywords and your own RSS/Atom feeds - get only NEW articles since the last run. Canonical publisher URLs, cross-source dedup, agent-ready JSON via API, MCP, or schedules. First run is a free baseline; you are billed only for new articles delivered. No API key, no browser.

- **URL**: https://apify.com/kaz\_kakyo/news-delta-api.md
- **Developed by:** [Heim AI](https://apify.com/kaz_kakyo) (community)
- **Categories:** News, Agents, Automation
- **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 article delivereds

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/platform/actors/running/actors-in-store#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

## News Delta API — only new articles from Google News + RSS/Atom

A **news API** and **RSS/Atom monitor** that returns **only new articles** since your last run. Point it at Google News keyword/topic queries and/or public feed URLs, give each monitor a stable `monitorId`, and every subsequent run delivers canonical, deduped article metadata you have not seen before — ready for an LLM, agent, webhook, or spreadsheet.

### Why delta

Pull scrapers and raw feed readers re-send the same headlines every poll. You end up building your own seen-set, URL normalizer, and cross-source dedup. This actor keeps a persistent per-`monitorId` seen-set in a named key-value store and returns the delta only: title, canonical publisher URL, source domain, published date, and snippet. No publisher full text (copyright + scope). First run is a free baseline (records state, delivers no article rows) unless you set `deliverOnFirstRun`.

### Quickstart (Console)

1. Open the actor → **Input**.
2. Keep the prefill query `artificial intelligence` or add your own `queries` / `feeds`.
3. Set `monitorId` (e.g. `ai-watch`) so later runs share the same seen-set.
4. **Start** — first run writes a `baseline` row. Run again with the same `monitorId` to receive `article` rows.

### Input reference

| Field | Type | Default | Notes |
|---|---|---|---|
| `queries` | string list | prefill `["artificial intelligence"]` | Google News search queries; supports `site:`, quotes, `OR`, `when:7d` |
| `feeds` | string list | `[]` | RSS 2.0 / Atom / JSON Feed URLs (http(s), public hosts only) |
| `monitorId` | string | `"default"` | Identity of the persistent seen-set |
| `maxArticlesPerQuery` | integer | `50` (max 100) | Newest-first cap per Google News query |
| `maxTotalNewArticles` | integer | `500` | Cap on delivered article rows per run |
| `language` | string | `"en"` | Google News `hl` |
| `country` | string | `"US"` | Google News `gl`; `ceid` = `country:language` |
| `deliverOnFirstRun` | boolean | `false` | If true, first run delivers + charges like a normal delta run |

### Output contract

Four row types in the default dataset:

#### `article` (charged as `new-article`)

```json
{
    "type": "article",
    "monitorId": "ai-watch",
    "sourceType": "query",
    "query": "artificial intelligence",
    "title": "Example headline",
    "url": "https://publisher.example/story",
    "urlIsGoogleRedirect": false,
    "sourceDomain": "publisher.example",
    "publishedAt": "2026-08-01T12:00:00.000Z",
    "snippet": "Plain-text snippet up to 500 chars",
    "firstSeenAt": "2026-08-01T13:00:00.000Z"
}
```

#### `baseline` (uncharged, first run only)

```json
{
    "type": "baseline",
    "monitorId": "ai-watch",
    "sources": [{ "source": "artificial intelligence", "sourceType": "query", "status": "ok", "itemsFetched": 50, "newItems": 50 }],
    "message": "Baseline recorded — subsequent runs deliver only new articles."
}
```

#### `summary` (uncharged, always last)

```json
{
    "type": "summary",
    "monitorId": "ai-watch",
    "baseline": false,
    "runCount": 2,
    "sources": [{ "source": "artificial intelligence", "sourceType": "query", "status": "ok", "itemsFetched": 50, "newItems": 3 }],
    "totals": { "sourcesPolled": 1, "newArticles": 3, "duplicatesSkipped": 47, "decodeFailures": 0 },
    "chargeLimitReached": false
}
```

#### `error` (uncharged)

```json
{
    "type": "error",
    "error": "no-sources",
    "message": "Provide at least one Google News query in `queries` or one feed URL in `feeds`.",
    "monitorId": "default"
}
```

Per-source failures (`sourceType: "query"|"feed"`) also emit error rows; the run still succeeds. Other run-level error codes (also uncharged, run still succeeds): `no-sources`, `concurrent-run` (another run currently holds this monitorId's lease — nothing polled or charged), `state-read-failed` / `state-write-failed` (monitor state store temporarily unavailable — nothing polled or charged).

### monitorId + baseline

- Same `monitorId` → shared seen-set across runs (and schedules).
- First run with a new `monitorId` (`runCount === 0`) and `deliverOnFirstRun: false`: polls all sources (charges `query-poll`), records every item into seen state, delivers **zero** article rows and **zero** `new-article` charges, pushes one `baseline` row.
- The baseline is completed **per source**. A source that failed, or was skipped for budget or time, was never read, so its existing catalogue was never recorded — it is baselined free on a later run instead of being billed as "new". Sources that did complete move to delta billing immediately, so one broken feed never silences the rest of the monitor. While any source is still unbaselined the `baseline`/`summary` rows carry `baselineIncomplete: true`.
- Later runs deliver only identities not in the seen-set (URL key, Google News id, or title hash within 14 days). Items with no title are identified by URL alone, so several untitled items from one feed are never collapsed into a single row.
- An item whose only identifier is an opaque `<guid>` or JSON Feed `id` (`urn:uuid:…`, `tag:…`, a bare number) carries no link, so it is skipped rather than delivered — and billed — with a URL that cannot be opened.
- Only one run at a time may hold a given `monitorId`. The lease is a server-side lock, so the second run exits successfully with a `concurrent-run` error row and charges nothing — two runs can never sell you the same article. A run that crashes releases its monitor within about 8 minutes; a run that is still alive keeps it. Schedule a monitor no more often than its runs take to finish.

### Recipes

#### Schedule every 30 minutes

Apify Schedules → this actor, interval 30 minutes, payload:

```json
{
    "queries": ["artificial intelligence", "site:reuters.com chips"],
    "feeds": ["https://techcrunch.com/feed/"],
    "monitorId": "ai-watch",
    "maxArticlesPerQuery": 50
}
```

#### API (`run-sync-get-dataset-items`)

```bash
curl -s "https://api.apify.com/v2/acts/kaz_kakyo~news-delta-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "content-type: application/json" \
  -d '{"queries":["artificial intelligence"],"monitorId":"api-demo"}'
```

First call baselines; second call returns new articles (plus summary).

#### MCP / agents

Works with the Apify MCP server — agents can call this actor as a tool with the same JSON input. Use a stable `monitorId` per watch topic so the tool returns deltas instead of full dumps.

### Pricing

Pay-per-event (configure prices in Console):

| Event | Meaning |
|---|---|
| `apify-actor-start` | Once per successful validated run (~$0.005) |
| `query-poll` | Per source (query or feed) successfully polled — 200 and 304 both count (~$1 / 1,000) |
| `new-article` | Per delivered article row (~$1 / 1,000) |

A quiet monitor that polls one source and finds nothing new costs roughly one start + one poll (~$0.006/poll at those list prices). Failed fetches and SSRF-blocked feeds are not charged as polls. Invalid empty input charges nothing.

### Honest limits

- Google News returns up to ~100 items per query; we cap with `maxArticlesPerQuery`.
- Canonical-URL decode uses a Google internal batchexecute path that can drift; on failure we still deliver the `news.google.com` redirect link with `urlIsGoogleRedirect: true`.
- No article full text — metadata and snippets only.
- Fuzzy title dedup uses a 14-day window (generic headlines can collide).
- Seen-set retention per monitor: 60 days, capped at 30,000 URL + 30,000 Google News + 10,000 title identities (oldest evicted first). An article resurfacing after falling out of that window is treated as new again.
- Each feed has a 20-second total fetch budget (redirects included) and a 5 MB body cap.
- Feeds must be publicly reachable; private/internal hosts (localhost, RFC1918, link-local, cloud metadata) are blocked by the SSRF guard. URLs with embedded `user:pass@` credentials are rejected — use a token query parameter if your feed needs auth.
- Authenticated feed URLs are never echoed back in full. Wherever a feed URL appears in output (article rows, error rows, summaries, logs) its query-parameter **values** are replaced with `***` — parameter names, host and path are kept, so `https://host/feed.xml?edition=uk&api_key=SECRET` is reported as `https://host/feed.xml?edition=***&api_key=***`. Credential-looking path segments (`/reset/SECRET/…`) and any `user:pass@` are masked the same way.

### Local development

```bash
npm install
node src/unit-check.js
apify run --purge -i '{"queries":["artificial intelligence"],"monitorId":"local-test"}'
```

# Actor input Schema

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

Keyword/topic/site queries to poll on Google News RSS. Supports site:, quotes, OR, and when:7d. Each query is one source poll.

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

Direct RSS 2.0, Atom, or JSON Feed URLs (http/https, public hosts only). Conditional GETs reuse ETag/Last-Modified across runs.

## `monitorId` (type: `string`):

Identity of the persistent seen-set. Same ID = only NEW articles after the baseline run. Different IDs keep separate state.

## `maxArticlesPerQuery` (type: `integer`):

Newest-first cap per query (Google News returns up to ~100).

## `maxTotalNewArticles` (type: `integer`):

Hard cap on delivered article rows (and new-article charges) in one run.

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

Google News language code (hl). Combined with country for ceid.

## `country` (type: `string`):

Google News country/region code (gl). ceid is built as country:language.

## `deliverOnFirstRun` (type: `boolean`):

If false (default), the first run for a monitorId is a free baseline: records seen state, delivers no articles, charges no new-article events. If true, the first run delivers and charges like a normal delta run.

## Actor input object example

```json
{
  "queries": [
    "artificial intelligence"
  ],
  "feeds": [],
  "monitorId": "default",
  "maxArticlesPerQuery": 50,
  "maxTotalNewArticles": 500,
  "language": "en",
  "country": "US",
  "deliverOnFirstRun": false
}
```

# Actor output Schema

## `articles` (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": [
        "artificial intelligence"
    ],
    "monitorId": "default"
};

// Run the Actor and wait for it to finish
const run = await client.actor("kaz_kakyo/news-delta-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 = {
    "queries": ["artificial intelligence"],
    "monitorId": "default",
}

# Run the Actor and wait for it to finish
run = client.actor("kaz_kakyo/news-delta-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "artificial intelligence"
  ],
  "monitorId": "default"
}' |
apify call kaz_kakyo/news-delta-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kaz_kakyo/news-delta-api",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hK8bjdX1tEPHK65Sg/builds/FMrQVakNN2bRDKsFz/openapi.json
