AnswerLine Start free

, News · Google · Tutorials

How to get Google News results as structured JSON

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

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 Default false. Adds html, a link to a copy of the feed the results were read from. No extra cost.

News targeting is by country and language. Google News covers 71 countries, listed in coverage. 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:

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:

  1. Quote multi-word names. "acme corp" asks for the exact phrase; without quotes the two words are searched as separate terms.
  2. 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.
  3. 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.
  4. Match the market’s language. Send local-language names and terms with the matching hl for each market; a translated English query can miss how local outlets write about you.
  5. 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

  1. 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.
  2. Run each query per market as an async task on a schedule.
  3. On each webhook, insert new articles; alert on inserts whose title or snippet matches the entity.
  4. Suppress repeats by url_key, and group alerts by source so 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:

  1. For each entity, combine the name with risk terms in separate queries ("Jane Example" fraud, "Jane Example" lawsuit), because a long OR chain returns a mixed set that is harder to review.
  2. Run in each country where the entity operates, with the local hl, since local-language coverage often appears there first.
  3. Store every observation, not only matches: an auditor may ask what the screen returned on a given date.
  4. 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

Current prices at the Starter tier (see /pricing for every tier):

Request Price
Google News, 1 page $0.0002
Google News, 3 pages $0.0002

A budget is queries × markets × runs per month × price per run. 50 queries in 2 markets, hourly (about 720 runs a month), one page: 50 × 2 × 720 × $0.0002 = $14.40. The same set every 6 hours is $2.40. Frequency is the largest lever, so match it to the table above rather than running everything at the fastest interval.

Pitfalls

  1. Treating source as a key. Publisher display names change and collide. Key on the normalized link domain.
  2. Parsing dates in one language. The date text follows hl. Keep the raw string and a first-seen timestamp.
  3. Alerting on every run. Alert on new url_key inserts, not on every observation.
  4. 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.

Questions

How do I get Google News search results as JSON?

Send POST /v1/monitor/google/news with a query and a country or gl, or create a GOOGLE_NEWS async task. The response lists the articles in result.newsResults[], each with position, title, link, snippet, source, date, page and thumbnail.

Can I choose the country and language for Google News results?

Yes. country or gl sets the result geography, and hl sets the interface language. Targeting is at country level.

How many pages of Google News results can one request return?

The pages field accepts 1 to 10 and defaults to 1. Pages after the first are free.

How much does a Google News request cost?

A Google News request costs $0.0002; additional pages are free. This is the Starter-tier price; higher 30-day spend reaches cheaper tiers.

How do I tell a new article from one I have already stored?

Normalize link into a key: lowercase the host, drop www., remove the fragment and tracking parameters and trim a trailing slash, but keep query parameters that identify the article. Store articles and appearances in separate tables, so a new article is an insert and a move is a change of position.

What is redirectLink on a news result?

When Google served an article link as a redirect, the result carries the resolved destination in link and Google's redirect in redirectLink. POST /v1/monitor/google/goto resolves a redirect link and charges nothing.

Try it on your own prompts

100 free requests on any engine, no card. One POST returns the answer, sources and citations as JSON.

Keep reading