The Google News API takes a query and returns the articles Google News shows for it, in order, as data: headline, publisher, link, snippet, date and thumbnail, for a chosen country and language. Below: the request and response, date normalization, storage and deduplication, and three patterns (brand alerts, coverage share, adverse media screening).
If you are deciding between RSS feeds and structured results, read Google News RSS feeds: what they give you and where they stop first.
Definitions
-
News result: one article on the Google News results page for a query, returned as an item of
result.newsResults[]. -
Position: the 1-indexed order of the article in the news results, returned with the
pageit appeared on. - Source: the publisher name as Google displays it, such as a newspaper or a trade site.
- Coverage share: the fraction of news results for a query set that belong to a given publisher or mention a given brand.
The request
curl -X POST https://api.answerline.dev/v1/monitor/google/news \
-H "Authorization: Bearer $ANSWERLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "acme corp recall",
"country": "DE",
"hl": "de",
"pages": 2
}'
Request fields:
| Field | Type | Notes |
|---|---|---|
query |
string, 1-10,000 chars | Required. The News search. |
country |
string | ISO 3166-1 alpha-2. Send country or gl. |
gl |
string | Google's name for the result geography; same codes, either case. Different values in country and gl are a 400. |
hl |
string | Interface language (en, de, pt-BR). Overrides the language derived from the country. |
pages |
integer 1-10 | Default 1. |
include.html |
boolean | Not available on Google News: a request that sets it is refused with a 400. |
News targeting is by country and language. Supported countries are listed by GET /v1/countries?model=google. Don't route News through the Google Search endpoint's url shape: a Google URL carrying tbm=nws is rejected there, because News has its own endpoint and pricing.
The response
{
"success": true,
"result": {
"newsResults": [
{
"position": 1,
"title": "Acme Corp expands recall to second product line",
"link": "https://news.example.de/acme-recall",
"snippet": "The company said on Tuesday that...",
"source": "Example Nachrichten",
"date": "3 hours ago",
"page": 1,
"thumbnail": "https://news.example.de/images/acme.jpg"
}
]
}
}
Field by field:
-
position: 1-indexed rank in the news results. Withpagesabove 1,pagetells you which page the article came from; store both. -
title,snippet: the headline and the text excerpt shown. -
link: the article URL. If Google served it as a redirect,redirectLinkcarries Google's redirect next to it. -
source: the publisher's display name. It is a name, not a domain; derive the domain fromlinkwhen you need a stable key. -
date: the date text as displayed, for example"2 hours ago". -
thumbnail: the image URL when one is shown. -
html[]: not returned for Google News;include.htmlis refused on this endpoint.
Normalizing dates
date is display text, such as "2 hours ago", so do not assume one format. Store three things: the raw string, the capture time, and a parsed estimate.
import re
from datetime import datetime, timedelta, timezone
UNITS = {"minute": "minutes", "hour": "hours", "day": "days", "week": "weeks"}
def published_estimate(raw: str, captured_at: datetime) -> datetime | None:
"""Parse English relative dates like '3 hours ago'. Returns None for formats it doesn't know."""
m = re.match(r"(\d+)\s+(minute|hour|day|week)s?\s+ago", raw.strip().lower())
if not m:
return None
return captured_at - timedelta(**{UNITS[m.group(2)]: int(m.group(1))})
captured = datetime.now(timezone.utc)
print(published_estimate("3 hours ago", captured))
Display text can depend on the interface language you request with hl, so a parser written for English may not match other languages. Rather than parse every format, keep None for the unknown ones and fall back to the first time you saw the article: for alerting, "first seen" is usually the timestamp that matters anyway.
Storing and deduplicating articles
The same article comes back on every run while it stays in the results. Split the data into what an article is and where it appeared:
create table news_articles (
url_key text primary key, -- normalized link
title text not null,
source text,
first_seen timestamptz not null,
published_at timestamptz -- estimate, nullable
);
create table news_observations (
run_id bigint not null,
query text not null,
country text not null,
url_key text not null references news_articles(url_key),
position int not null,
page int not null,
date_raw text,
captured_at timestamptz not null,
primary key (run_id, query, country, url_key)
);
Normalize link into url_key by lowercasing the host, dropping www., removing the fragment and common tracking parameters (utm_*), and trimming a trailing slash. Do not strip all query parameters: some publishers identify articles by them.
With this split, "new article" is an insert into news_articles, and "article moved up" is a comparison of position between two observations of the same url_key.
Writing queries that return what you mean
The query is the largest source of noise in news monitoring. Quotes for exact matches and - for exclusions are both documented in Google's Refine web searches help page (checked 2026-09-17). A few rules keep result sets reviewable:
-
Quote multi-word names.
"acme corp"asks for the exact phrase; without quotes the two words are searched as separate terms. -
Exclude known collisions. If your brand shares a name with a place, a film or a sports team, add
-terms for the collision ("acme" -cartoon) and review what the exclusion removes on the first few runs. - One intent per query. Keep "brand news", "brand plus product", and "brand plus risk terms" as separate queries so each result set answers one question and can have its own schedule.
-
Match the market's language. Send local-language names and terms with the matching
hlfor each market; a translated English query can miss how local outlets write about you. - Version the query list. Store a version with every observation, so a change in results can be separated from a change in the query.
Pattern 1: brand and competitor alerts
- Write one query per entity and variant: the brand name in quotes, the brand plus key product names, and the executives' names if they are public figures.
- Run each query per market as an async task on a schedule.
- On each webhook, insert new articles; alert on inserts whose
titleorsnippetmatches the entity. - Suppress repeats by
url_key, and group alerts bysourceso one story syndicated across many outlets arrives as one message with a list.
Quoted phrases and operators such as - go inside query exactly as you would type them into Google. Google search operators lists the ones worth using.
Pattern 2: coverage share
Coverage share answers "who owns the news results for our topics?". For a fixed query set, count results per publisher domain or per brand mention and divide by all results:
from collections import Counter
from urllib.parse import urlparse
def publisher_share(results: list[dict], top_n: int = 10) -> list[tuple[str, float]]:
"""results: newsResults items from one run of a query set."""
domains = Counter((urlparse(r["link"]).hostname or "").removeprefix("www.")
for r in results if r.get("page") == 1 and r.get("position", 99) <= top_n)
total = sum(domains.values()) or 1
return [(d, n / total) for d, n in domains.most_common()]
Limit to the first page or the top 10 positions so a query with more results does not dominate. Track the share weekly: a PR team sees whether coverage is concentrated in a few outlets, and a publisher sees which competitors win the topics it covers.
Pattern 3: adverse media screening
Compliance and risk teams screen names against negative news. The shape:
- For each entity, combine the name with risk terms in separate queries (
"Jane Example" fraud,"Jane Example" lawsuit), because a longORchain returns a mixed set that is harder to review. - Run in each country where the entity operates, with the local
hl, since local-language coverage often appears there first. - Store every observation, not only matches: an auditor may ask what the screen returned on a given date.
- Route matches to a reviewer; do not auto-decide on a headline.
The adverse media screening use case describes the workflow end to end.
Scheduling
News moves quickly, but not every query needs the same frequency. A practical split:
| Query type | Suggested interval | Why |
|---|---|---|
| Active incident or launch | 15-60 minutes | Pickup speed matters |
| Brand and executive names | Every few hours | Catch stories the same day |
| Topic and competitor coverage | Daily | Share changes slowly |
| Screening of third parties | Daily or weekly | Driven by policy |
Send each run as one batch of async tasks (up to 500 per request) with a webhook, so nothing polls. Use an idempotencyKey built from the run slot, query id and market, so a retried scheduler run creates no duplicates. See sync, async and webhooks and async tasks.
curl -X POST https://api.answerline.dev/v1/async/task/batch \
-H "Authorization: Bearer $ANSWERLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{ "taskType": "GOOGLE_NEWS", "idempotencyKey": "news-2026-09-15T10-acme-de",
"payload": { "query": "\"acme corp\"", "country": "DE", "hl": "de" },
"webhook": { "url": "https://your-app.example/hooks/news" } },
{ "taskType": "GOOGLE_NEWS", "idempotencyKey": "news-2026-09-15T10-acme-us",
"payload": { "query": "\"acme corp\"", "country": "US" },
"webhook": { "url": "https://your-app.example/hooks/news" } }
]'
Cost in credits
From the current credit table (see /pricing for credit prices):
| Request | Credits |
|---|---|
| Google News task, 1 page | 2 |
| Google News task, 3 pages | 6 |
| Google News synchronous call, 1 page | 4 |
A budget is queries × markets × runs per month × credits per run. 50 queries in 2 markets, hourly (about 720 runs a month), one page as async tasks: 50 × 2 × 720 × 2 = 144,000 credits. The same set every 6 hours is 24,000. Frequency is the largest lever, so match it to the table above rather than running everything at the fastest interval.
Pitfalls
-
Treating
sourceas a key. Publisher display names change and collide. Key on the normalized link domain. -
Parsing dates in one language. The date text follows
hl. Keep the raw string and a first-seen timestamp. -
Alerting on every run. Alert on new
url_keyinserts, not on every observation. - Using long OR chains. Split risk terms into separate queries so each result set is reviewable.
Field reference: Google News engine page. More patterns: news monitoring.
Top comments (0)