# Google Trends Monitor: Spike Alert, Trending Now & API (`solidfetch/google-trends-monitor`) Actor

Google Trends monitor: trending searches now in any country (US, Germany, UK, India...) with traffic and news links, plus a daily keyword watchlist that saves only what changed: interest spikes, rising related queries and a changed flag for up to 50 keywords. Pay per result.

- **URL**: https://apify.com/solidfetch/google-trends-monitor.md
- **Developed by:** [Solid Fetch](https://apify.com/solidfetch) (community)
- **Categories:** SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 94.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 1,000 trends

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

## Google Trends Monitor: Trending Now and Daily Keyword Watchlist

Three jobs in one actor. The first lists what people in a country are searching for right now, with the rough number of searches, when the trend started and the news articles Google shows next to it. The second watches a list of up to 50 keywords and saves only the ones that changed since the last run: the latest interest value, how far it moved, and the related queries that started rising since yesterday.

The third compares up to 5 keywords at a time on one 0 to 100 scale over a timeframe, with the average, the peak, each keyword's share of the group, interest by region and related queries, one clean row per keyword (see "Compare keywords (interest over time)" below).

It reads the same public Google Trends endpoints that the Trends website uses. There is no browser and no login. The data is unofficial, see Limits below.

### What people use it for

- **A daily "what is trending" feed.** Pull the top trending searches for one or more countries every morning. Each row has the search term, a rank, a volume bucket such as `20K+`, the start time and up to three news articles with title, URL and source. Newsrooms, content teams and newsletter writers use it to spot topics early.
- **A watchlist that tells you what moved.** Put 20 brand names, products or topics in `keywords` and schedule the actor daily. After the first run you get rows only for keywords that moved by at least the threshold or that got a new rising related query. Nothing is saved or charged for the quiet ones.
- **Rising query alerts.** Google lists "rising" related queries, often marked Breakout. The actor remembers last run's list and gives you `newRisingQueries`, the ones that were not there before.
- **A trend verdict per keyword.** Every row says whether interest is `rising`, `falling` or `stable` against the previous weeks, whether that move is unusual for the keyword (`significant`), and gives a one-line `summaryText` you can paste into a chat or an e-mail.
- **A spike signal on the first run.** Every row has `median30d`, `spikeRatio` and `isSpike`, so a keyword that is 2 or 3 times its usual level is flagged on the baseline run, without waiting for a second one.
- **Where the interest is.** Turn on `includeRegions` and each saved keyword row also lists the 10 regions of the country with the highest interest, for example the US states where a product is searched most. Set `maxRegions` up to 250 to get every region Google lists, with its rank. It is asked only for the keywords that are saved in that run, so quiet keywords cost no extra requests.
- **Which keyword wins.** Put 2 to 5 brands, products or topics in one group and get one row per keyword on the same scale, so "bitcoin 62, ethereum 25" means what it says. `shareOfSearch` says how much of the group's interest each keyword has.
- **Market research by country.** Run the same trending list for PL, DE, GB and US and compare what is hot in each market. Language and country are inputs.

### How to use it

Trending now for two countries:

```json
{
    "mode": "trending",
    "countries": ["PL", "US"],
    "maxItems": 25,
    "hours": "24"
}
```

A daily watchlist. Run it once to create the baseline, then schedule it:

```json
{
    "mode": "monitor",
    "keywords": ["bitcoin", "iphone 17", "electric bike"],
    "country": "US",
    "timeframe": "now 7-d",
    "monitorName": "my-watchlist",
    "onlyChanged": true
}
```

Compare keywords over the past 12 months, two groups, each on its own scale:

```json
{
    "mode": "compare",
    "comparisons": [["bitcoin", "ethereum", "solana"], ["iphone", "pixel"]],
    "country": "US",
    "timeframe": "today 12-m",
    "includeRegions": true,
    "includeRelatedQueries": true
}
```

A field name the actor does not know stops the run with a hint such as `"contry" (did you mean "country"?)`, so a typo cannot turn into a run that ignores a setting. The same goes for a setting that belongs to the other mode, for example `countries` with `mode` set to `monitor`. Leave those at their defaults and the run starts.

### Input

| Field | What it does |
|---|---|
| `mode` | `trending` (default), `monitor` or `compare`. |
| `countries` | Trending only. Two-letter country codes such as `US`, `PL`, `DE`, `GB`. Up to 50. Default `US`. A code that is not a country stops the run before any request. A country Google Trends has no trending page for (for example `IS` or `MT`), or one that does not answer within about 90 seconds, is skipped, named in the run message, not charged, and the other countries still run. If no country answers in time, or three in a row do not, Google is blocking the run or down, so the run stops with an error instead of ending empty. |
| `maxItems` | Trending only. Rows per country, in Google's order, 1 to 500. Default 25. With `onlyActive` on, the list is cut after the ended trends are removed, so you still get up to `maxItems` active ones. |
| `hours` | Trending only. How far back Google looks: `"4"`, `"24"`, `"48"` or `"168"` hours. Give it as text, as the input form does. Default `"24"`. |
| `onlyActive` | Trending only. Google also lists searches whose peak has already ended. Turn this on to keep only the ones still running. Default off. |
| `minTraffic` | Trending only. Keep trends with at least this many searches, for example `5000`. Google gives a bucket, and the bucket is compared. Trends without a figure are dropped when this is above 0. Default 0, which keeps all. Dropped trends are not saved and not charged, and `maxItems` counts what is left. |
| `categories` | Trending only. Keep trends in at least one of these Google categories, for example `["Sports", "Business and Finance"]`. Any case works, and `&` for `and`. The form field is a free text list, so lower case such as `sports` and `business & finance` are accepted. A name Google does not use stops the run and lists the allowed ones. Default empty, which keeps all. Rows from the RSS fallback have no categories, so this filter drops them. |
| `includeArticles` | Trending only. News articles per trend. Default on. Google's main feed currently sends none, so they are taken from the country's RSS feed and attached to the trends it also lists (up to three each), and other trends have an empty `articles` list. |
| `language` | Trending only. Two-letter language code sent to Google. Default `en`. |
| `comparisons` | Compare only. A list of keyword groups, for example `[["bitcoin", "ethereum"], ["iphone", "pixel"]]`. Each group has 1 to 5 keywords, as Google allows, and up to 20 groups per run. The keywords of a group share one 0 to 100 scale, and every group has a scale of its own. A repeated keyword inside a group, and a repeated group, are dropped. |
| `keywords` | Monitor: up to 50 keywords or phrases, 100 characters each. Compare: one group of up to 5, when you do not give `comparisons`. Giving both stops the run. Duplicates are removed, whatever their case, spacing or accent spelling. Polish letters and emoji work. |
| `country` | Monitor and compare. Two-letter code such as `US`, a region such as `US-CA`, or empty for worldwide. A US region that is not a state or territory code, such as `US-ZZ`, stops the run before any request, with a message that names it. |
| `timeframe` | Monitor and compare. `now 1-d`, `now 7-d` (default), `today 1-m`, `today 3-m` or `today 12-m`. Phrases such as `7 days` or `30 days` also work. |
| `property` | Monitor and compare. Which Google search the interest is measured in: `web` (default), `youtube`, `news`, `images` or `shopping` (Google Shopping, the best signal for physical product demand). Any case works, and `Google Shopping` or `video` are understood. An unknown value stops the run before any request and lists the allowed ones. Each property is a separate series with its own baseline and its own state, so switching it never mixes with the history of web search. Results for `youtube`, `news`, `images` and `shopping` are verified in the cloud after release. |
| `monitorName` | Monitor only. Name of the saved state. Runs with the same name are compared with each other. Default `default`. Use one name per watchlist, and never run two lists at the same time under one name. The old field name `stateKey` still works. |
| `onlyChanged` | Monitor only. Save only keywords that are new, moved by the threshold or have new rising queries. Default on. |
| `changeThreshold` | Monitor only. Points on the 0 to 100 scale that the latest value must move to count as changed. Default 10. `0` means any move. |
| `spikeThreshold` | Monitor only. A row gets `isSpike` `true` when the latest value is at least this many times its 30-day median. Default `1.5`, from 1 to 100. |
| `maxRegions` | Monitor and compare. How many regions `includeRegions` lists, 1 to 250. Default 10. Above 10 each entry also has a `rank`. Needs `includeRegions`, and the run stops with a message if it is set while that is off. |
| `includeRelatedQueries` | Monitor and compare. Top and rising related queries. Costs one more Google request per keyword. Default on, which in compare means one more request for every keyword of every group, so turn it off for the fastest comparison. |
| `includeRelatedTopics` | Monitor only. Up to 10 top and 10 rising related topics (entities such as a company, a person or a product type) next to the related queries. One more Google request per saved keyword. Default off. See Limits. |
| `includeRegions` | Monitor and compare. The sub-regions of `country` with the highest interest (10 by default, see `maxRegions`), for example US states or Polish voivodeships. Worldwide lists countries. One more Google request per saved keyword, and in compare one request per group. Default off. |
| `includeTimeline` | Monitor only. Compare rows always have the series. Adds every point of the interest-over-time series to the row. Default off. |
| `proxyConfiguration` | Apify Proxy settings. See Limits. |

### Output

#### Trending rows

One row per trending search. Real values from a run for Poland on 7 October 2026:

```json
{
    "country": "PL",
    "rank": 1,
    "query": "iga świątek",
    "approxTraffic": 20000,
    "approxTrafficLabel": "20K+",
    "startedAt": "2026-10-07T05:20:00.000Z",
    "endedAt": null,
    "isActive": true,
    "volumeGrowthPercent": 900,
    "categories": ["Sports"],
    "relatedQueries": ["iva jovic", "świątek", "iga swiatek"],
    "articles": [
        {
            "title": "WTA Pekin: Iva Jovic - Iga Świątek [RELACJA NA ŻYWO]",
            "url": "https://przegladsportowy.onet.pl/tenis/iva-jovic-iga-swiatek-relacja-na-zywo-wta-pekin-live/m5krdpg",
            "source": "Przegląd Sportowy",
            "publishedAt": "2026-10-07T04:30:00.000Z"
        }
    ],
    "source": "batchexecute"
}
```

The lists of related searches and articles are shortened here, and `fetchedAt` is left out.

| Field | Meaning |
|---|---|
| `country`, `rank` | Country code and the position in Google's own order for that country. |
| `query` | The trending search term. |
| `approxTraffic`, `approxTrafficLabel` | The volume bucket Google shows, for example 20000 and `20K+`. It is a bucket, not an exact count. |
| `startedAt`, `endedAt`, `isActive` | When the trend started and, if it already peaked, when it ended. `isActive` is `true` while it runs. |
| `volumeGrowthPercent` | The growth figure Google attaches to the trend. |
| `categories` | Google's topic names, such as Sports or Politics. |
| `relatedQueries` | Other search terms Google groups under the same trend. Often empty. |
| `articles` | News articles with `title`, `url`, `source` and `publishedAt`. `publishedAt` is `null` for articles taken from the RSS feed, which has no article time. Empty when `includeArticles` is off. |
| `source` | `batchexecute` for the main feed, `rss` when the fallback feed was used. |
| `fetchedAt` | When the run asked Google. |

If the main feed fails for a country, the actor falls back to Google's public trending RSS feed for that country. The rows say `rss` in `source`, and `isActive`, `endedAt`, `categories` and `volumeGrowthPercent` stay empty because the RSS feed does not have them. The run summary and the log say that the fallback was used, and the failure is reported to the developer.

#### Keyword monitor rows

One row per saved keyword. This example comes from the real answer Google gave for "bitcoin" in the US on 7 October 2026. Two values were edited (the last hour and one rising query) to show what a changed row looks like. The four trend fields (`direction`, `changePct`, `significant`, `summaryText`) show the format only and were written by hand, not computed from that answer. The lists are shortened.

```json
{
    "keyword": "bitcoin",
    "country": "US",
    "timeframe": "now 7-d",
    "property": "web",
    "latestValue": 78,
    "latestAt": "2026-10-07T06:00:00.000Z",
    "latestIsPartial": false,
    "averageValue": 57.4,
    "peakValue": 100,
    "median30d": 54,
    "spikeRatio": 1.44,
    "isSpike": false,
    "direction": "rising",
    "changePct": 34.2,
    "significant": true,
    "summaryText": "bitcoin: rising +34% vs previous 4 weeks",
    "previousValue": 63,
    "change": 15,
    "changePercent": 24,
    "changed": true,
    "changeType": "changed",
    "newRisingQueries": ["bitcoin etf news"],
    "risingQueries": [{ "query": "bitcoin etf news", "value": 3000, "label": "Breakout" }],
    "topQueries": [{ "query": "bitcoin price", "value": 100, "label": "100" }],
    "previousCheckedAt": "2026-10-07T08:00:00.000Z",
    "checkedAt": "2026-10-08T08:00:00.000Z",
    "exploreUrl": "https://trends.google.com/trends/explore?q=bitcoin&date=now+7-d&geo=US"
}
```

| Field | Meaning |
|---|---|
| `keyword`, `country`, `timeframe`, `property` | What was checked. An empty `country` means worldwide. `property` is the search the numbers come from, `web` unless you chose another. |
| `latestValue`, `latestAt` | Interest at the last complete point of the series, on Google's 0 to 100 scale. `latestIsPartial` is `true` only if every point is still running. On the `now 1-d` and `now 7-d` timeframes the last point is always treated as still running, because Google does not always flag it and its value can be 0 and then 30 a minute later. It is left out of `latestValue`, of the change check and of the spike signal. |
| `averageValue`, `peakValue` | Average and peak of the whole series. |
| `median30d`, `spikeRatio`, `isSpike` | The spike signal, see below. `median30d` is the median of the complete points in the 30 days before the latest point. `spikeRatio` is `latestValue` divided by it, rounded to two decimals. `isSpike` is `true` when the ratio is at least `spikeThreshold`. Both are `null` and `isSpike` is `false` when there is no earlier point or the median is 0. |
| `direction`, `changePct`, `significant` | The trend signal, see "Rising, falling or stable" below. `changePct` compares the last 7 days with the 28 days before them, so it is not the same as `changePercent`, which compares with your last run. All three are `null` when the series is too short. |
| `summaryText` | One sentence per keyword, for example `coffee: rising +34% vs previous 4 weeks, spike now (2.6× median)`. |
| `previousValue`, `change`, `changePercent` | The reference value and the difference to it. The reference is the value at the baseline or at the last time the keyword was reported as moved, not necessarily the value of the run before. All empty on a baseline row. |
| `changeType`, `changed` | `baseline`, `changed` or `unchanged`. `changed` is `true` only for `changed`. |
| `newRisingQueries` | Rising queries that were not in last run's rising list. |
| `risingQueries`, `topQueries` | Google's related queries. `value` is the score and `label` is Google's text, for example `Breakout` or `+350%`. Empty lists when Google has none, and `null` when `includeRelatedQueries` is off. |
| `regions` | Only with `includeRegions`. Up to `maxRegions` entries (10 by default) `{ geoCode, region, value }`, highest first, with a `rank` in front when `maxRegions` is above 10. `value` is Google's 0 to 100 score for interest as a share of all searches in that region, so a small region can top a large one. Regions without data are left out. `geoCode` is `null` for places Google gives no code, such as cities. `null` when Google did not answer. |
| `risingTopics`, `topTopics` | Only with `includeRelatedTopics`. Up to 10 entries each `{ topic, type, mid, value, label }`. `type` is Google's description such as `Company` and `mid` is Google's topic id. Empty lists when Google has none, and `null` when Google did not answer. |
| `timeline` | Only with `includeTimeline`. List of `{ time, value }`. |
| `previousCheckedAt`, `checkedAt`, `exploreUrl` | When the reference value was recorded, the time of this check and a link to the same search on Google Trends. |

Regions and related topics are extras of a saved row. If Google fails to answer for them, or answers in an unknown format, the row is still saved (and charged as usual) with `null` in that field, and the log has one warning for the keyword.

The run also saves a `SUMMARY` record in the key-value store. It counts baselines, changed and unchanged keywords, lists keywords with no data and keywords that failed, and shows how many Google requests the run made and how many were blocked.

### Compare keywords (interest over time)

Mode `compare` answers "which of these keywords is searched more, and how did that move over time". Give it groups of 1 to 5 keywords. For each group the actor asks Google for one comparison, so the keywords of a group are measured against each other: the highest point of the whole group is 100, and every other value says how big it is next to that peak. Values of different groups are not comparable. To put two groups on one scale, repeat a keyword in both and rescale by its values.

One row per keyword. This is a hand-written example in the format of the real answer, not a recorded run, and the values and the lists are shortened:

```json
{
    "group": 1,
    "keyword": "bitcoin",
    "comparedWith": ["ethereum", "solana"],
    "country": "US",
    "timeframe": "today 12-m",
    "property": "web",
    "timeline": [
        { "date": "2025-10-19T00:00:00.000Z", "value": 61 },
        { "date": "2025-10-26T00:00:00.000Z", "value": 58 },
        { "date": "2025-11-02T00:00:00.000Z", "value": 100 }
    ],
    "average": 64.2,
    "peak": 100,
    "peakDate": "2025-11-02T00:00:00.000Z",
    "shareOfSearch": 71.5,
    "direction": "falling",
    "changePct": -21.4,
    "significant": true,
    "summaryText": "bitcoin: falling -21% vs previous 4 weeks",
    "regions": [{ "geoCode": "US-NV", "region": "Nevada", "value": 100 }],
    "relatedQueries": {
        "top": [{ "query": "bitcoin price", "value": 100, "label": "100" }],
        "rising": [{ "query": "bitcoin etf", "value": 3000, "label": "Breakout" }]
    },
    "checkedAt": "2026-10-22T08:00:00.000Z",
    "exploreUrl": "https://trends.google.com/trends/explore?q=bitcoin%2Cethereum%2Csolana&date=today+12-m&geo=US"
}
```

| Field | Meaning |
|---|---|
| `group` | The position of the comparison in `comparisons`, from 1. Rows with the same `group` share one scale. |
| `keyword`, `comparedWith` | The keyword of the row and the other keywords of its group. |
| `country`, `timeframe`, `property` | What was compared. An empty `country` means worldwide. |
| `timeline` | Every point of the series as `{ date, value }`, on the shared 0 to 100 scale of the group. |
| `average`, `peak`, `peakDate` | Average and highest value of the series on that scale, and the date of the first point at the peak. |
| `shareOfSearch` | The average of the keyword divided by the sum of the averages of its group, in percent (0 to 100) to one decimal. The shares of a group add up to 100, and a group with one keyword has 100. It is `null` when the whole group is at 0. It is computed on all keywords of the group, also when a row of the group was saved before a restart or a neighbour has no data. |
| `direction`, `changePct`, `significant`, `summaryText` | The same trend signal as in the monitor rows, see "Rising, falling or stable". It compares the last 7 days with the 28 days before, on the shared scale. |
| `regions` | Only with `includeRegions`. Up to `maxRegions` entries `{ geoCode, region, value }` for this keyword, highest first. The values are on the shared scale of the group, so a region where another keyword dominates shows a low value for this one. `null` when Google did not answer. |
| `relatedQueries` | Only with `includeRelatedQueries`. `{ top, rising }` lists of `{ query, value, label }` for this keyword. `null` when Google did not answer. |
| `checkedAt`, `exploreUrl` | When Google was asked, and a link to the same comparison on Google Trends. |

How a run goes:

- Requests per group: one explore and one interest-over-time request for the whole group, so the cost of a comparison does not grow with the number of keywords. `includeRegions` adds one request per group and `includeRelatedQueries` one per keyword, both only for a group that still has rows to save.
- A group with more than 5 keywords, an empty group, a group that is not a list, both `comparisons` and `keywords`, or a setting of another mode stops the run before any request, with a message that names the group. Nothing is charged.
- A keyword that Google has no data for is not saved and not charged, is named in the `SUMMARY` record under `noData`, and counts as 0 in the shares. A group that fails after the retries is not charged and is listed under `failed`. Three groups in a row that fail stop the run, as with the monitor.
- If the platform migrates or restarts the run, the rows saved before are not saved or charged again, also when only part of a group was saved.
- Region and related query failures leave `null` in that field with one warning, and the row is saved as usual.

### The spike signal

The spike fields come from the interest series the actor already fetches for every keyword, so they cost no extra request and no extra charge, and they are on every saved row, including the baseline row of a first run and every `onlyChanged` off row.

- The reference is the median of the complete points in the 30 days before the latest point. The latest point itself and the running period are left out, so a spike does not lift its own baseline. A series shorter than 30 days, such as `now 1-d` or `now 7-d` with hourly points, uses every earlier complete point. With `today 12-m` the points are weekly, so the median covers the last four or five weeks.
- The signal is about the shape of the chosen timeframe. Pick `today 1-m` or `today 3-m` for a daily "is today unusual" check, and `now 7-d` for an hourly one.
- On `now 1-d` and `now 7-d` the last point is skipped as well, even when Google does not mark it as partial, so a keyword is not flagged because of a value that is still filling in.
- It does not change what counts as `changed`. A keyword that spikes but moved less than `changeThreshold` since the last run is still `unchanged`, and with `onlyChanged` on it is not saved. Set `onlyChanged` off to get `isSpike` for every keyword on every run.

### Rising, falling or stable

Every row has `direction`, `changePct` and `significant`, computed from the same timeline as the spike signal, with no extra request and no extra charge.

- **Windows.** The recent window is the last 7 days up to the latest complete point. The baseline is the 28 days before it. On a series shorter than 35 days (hourly `now 1-d` and `now 7-d`) the recent window is a fifth of the series and the baseline the four fifths before it. The running period (a partial last point) is never used.
- **`changePct`** is the mean of the recent window against the mean of the baseline, in percent, to one decimal. With `today 12-m` the points are weekly, so the recent window is the latest week alone and the baseline the four weeks before it.
- **`direction`** is `rising` when `changePct` is +15 or more, `falling` when it is -15 or less, otherwise `stable`.
- **`significant`** is `true` when the recent mean is more than two standard deviations of the baseline points away from the baseline mean, so a move that is large for a calm keyword is flagged and the same move on a keyword that swings a lot is not. `summaryText` adds "within normal swings" to a rising or falling verdict that is not significant.
- **Limits.** It is a rule of thumb, not a statistical test. The points of one series depend on each other, Google rescales every series to 0 to 100, and with `today 12-m` the baseline has only four points, so the standard deviation is rough. A baseline mean of 0 gives `changePct` `null`, and any recent interest is then `rising`. A baseline with fewer than four points gives `null` for all three fields.
- Rows saved before this feature, and all existing fields, are unchanged. The fields are added to the end of the spike group only.

### How the monitor decides what changed

- State is kept in the named key-value store `google-trends-monitor-state` on your account, in one record per `monitorName`. For each keyword, country, timeframe and property it holds the latest value, the reference value, the rising queries of the last run and the check time.
- The **first run** for a keyword is a baseline. It is always saved, with `changeType` `baseline`, and its value becomes the reference value.
- Later runs compare the latest value with the **reference value**, which is the value the last time you were told about the keyword. A keyword is `changed` when the latest value is at least `changeThreshold` points away from the reference, or when Google lists a rising query that was not in the last run's list.
- The reference value moves only when a row reports a move of at least the threshold. A series that climbs 5 points a day does not alert on day 1, but it does on day 2, because the total is 10 points away from the reference. After the alert the reference is the new value. A row that is `changed` only because of a new rising query keeps the old reference.
- Rising queries are compared with the list of the run before, so a rising query is reported once.
- With `onlyChanged` on (the default), unchanged keywords are not saved and not charged. The latest value and the rising queries of unchanged keywords are still remembered, and the reference is kept.
- With `onlyChanged` off, every keyword is saved and charged on every run, and unchanged ones have `changeType` `unchanged`.
- Change `country`, `timeframe` or `property` for a keyword and it starts a new baseline, because values on a different scale cannot be compared.
- A keyword is remembered only if its row was saved or it was skipped as unchanged. If your cost limit stops a run, the keywords it did not get to are reported again next time. Keywords with no data and keywords that failed are never remembered.
- Several runs can share one `monitorName` only if they never overlap in time. At every save the actor reads the state again and merges its own keywords into it, so runs that finish at different moments keep each other's keywords. Runs that check the same keywords at the same time overwrite each other, and the log says so. The safe way is one `monitorName` per list.
- A saved state that cannot be read stops the run before any request. The error says to use a new `monitorName` or to delete that record in the key-value store. Keywords then start a new baseline.

### Pricing

You pay per result, and the prices are on the Pricing tab. There is no start fee beyond the platform default.

| Event | Charged for | Price |
|---|---|---|
| `trend` | Each trending row saved | $0.0005 per row on the free plan ($0.0004 Bronze, $0.0003 Silver, $0.00025 Gold and above) |
| `keyword` | Each keyword row saved by the monitor, and each keyword row saved by compare | $0.003 per keyword on the free plan ($0.0025 Bronze, $0.002 Silver, $0.0015 Gold and above) |

`includeRegions`, `includeRelatedQueries` and `includeRelatedTopics` add fields to the `keyword` row. They are not charged as separate events. Compare has no event of its own: a compared keyword is charged as one `keyword`, whatever the size of its group, so a group of 5 costs 5 `keyword` events.

These are not charged:

- keywords that were checked but are unchanged and skipped by `onlyChanged` (with `onlyChanged` off they are saved, so they are charged)
- keywords for which Google has no data (also inside a compared group)
- compared groups that failed after the retries
- keywords that failed after the retries
- trending countries for which Google returns nothing, has no trending page, or does not answer in time
- anything past your maximum cost per run
- rows saved before the platform migrated or restarted the run: the resumed run does not save or charge them again

When a run reaches the maximum cost you set, it stops and keeps what was saved. It never saves a row the remaining budget cannot pay for.

### Limits

- **This is unofficial Google data.** The actor reads the same endpoints the Google Trends website calls. It is not affiliated with Google and uses no official API. Google can change these endpoints without notice. When the response format changes, the actor never returns guessed data: trending searches come from the RSS fallback when it works, and otherwise the run stops with an error that says what changed. You are responsible for using the data within Google's terms.
- **Related topics are often empty.** Google returned an empty list of related topics to sessions that are not logged in in both test sessions run while building this actor. `includeRelatedTopics` passes on whatever Google returns, so expect empty lists. Related queries (top and rising) do work.
- **Interest values are relative.** They run from 0 to 100 inside the chosen country and timeframe, where 100 is the peak of that window. Google also samples its data, so the same search can differ by a few points between runs. Compare a keyword with itself over time and do not compare values between different timeframes.
- **Volume is a bucket.** `20K+` means Google puts the trend in the 20,000 bucket. It is not an exact count.
- **Trending lists include ended trends.** In the runs recorded for this actor, 27% of the 125 trends for Poland and 19% of the 475 for the US were still active over the past 24 hours. Use `onlyActive` for a "right now" list.
- **Keywords with almost no data are skipped.** If Google marks fewer than 5% of the points of a series as having data (and at least three points are needed), the keyword counts as no data. A nonsense phrase in a recorded run came back with 2 data points out of 169.
- **Timeframes stop at 12 months.** The actor offers `now 1-d`, `now 7-d`, `today 1-m`, `today 3-m` and `today 12-m`. Google also has shorter and longer windows (the last hour, 4 hours, 5 years and all time), but no recorded answer of Google in this project uses them, so the exact request values could not be checked without sending a request, and they are not offered.
- **Speed.** The actor waits at least 3 seconds between Google requests and checks keywords one after another. Each keyword needs three requests (explore, interest over time, related queries), or two when the series has no data and each run starts with one extra request that Google answers with a rate limit page to hand out a cookie. Waiting alone takes about 7.5 minutes for 50 keywords with related queries, about 5 minutes without them. `includeRegions` and `includeRelatedTopics` add one request each per saved keyword, so a first run of 50 keywords with both takes about 5 minutes longer.
- **Blocks.** Google answers with a rate limit page when one IP sends too many requests. The actor retries four times, with 5, 10, 20 and 40 second backoff. With the default Apify Proxy, the first CAPTCHA or repeated rate limit moves the run to Apify residential proxy if your account can use it. If three keywords in a row fail, or three trending countries in a row get no answer in time, the run stops with an error instead of burning more requests. In the last local test, 12 requests with 20 seconds between them from a home connection were all answered and none was blocked. Earlier tests saw the first request of a session answered with a 429 that hands out a cookie. The actor expects it, repeats the request at once and does not count it as a block.
- **Compare speed.** A group needs two requests, 6 seconds of waiting, and with `includeRegions` and `includeRelatedQueries` on, a group of 5 keywords needs 8. Twenty such groups take about 8 minutes.
- **Language.** Search terms stay in the language of the country. The recorded answer for Poland with `language` set to `en` has Polish terms and Polish news.

When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values or your results, is sent to the developer to fix bugs.

# Actor input Schema

## `mode` (type: `string`):

Trending now lists the searches that are rising in a country right now. Keyword monitor checks your keywords and saves the ones that changed since the last run with the same state key. Compare keywords puts up to 5 keywords per group on one 0 to 100 scale over a timeframe.

## `countries` (type: `array`):

Two-letter country codes such as US, PL, DE or GB. One request per country, about 3 seconds apart. Used by mode Trending now.

## `maxItems` (type: `integer`):

Trending rows to save for each country, in Google's order. In test runs Google returned 121 trends for Poland and 475 for the US over 24 hours. Used by mode Trending now.

## `hours` (type: `string`):

How far back Google looks for rising searches.

## `onlyActive` (type: `boolean`):

Google also lists searches whose peak has already ended. Turn this on to keep only the ones still running. The RSS fallback has no end time, so its rows are always kept.

## `minTraffic` (type: `integer`):

Keep only trends with at least this many searches. Google gives a bucket such as 5000 (shown as 5K+), and the bucket is compared. Trends without a figure are dropped when this is above 0. 0 keeps everything. Dropped trends are not saved and not charged, and the list is cut to the maximum after they are dropped.

## `categories` (type: `array`):

Keep only trends that Google puts in at least one of these categories, for example Sports or Business and Finance. Any case works, and & for and. A name Google does not use stops the run and lists the allowed ones: Autos and Vehicles, Beauty and Fashion, Business and Finance, Entertainment, Food and Drink, Games, Health, Hobbies and Leisure, Jobs and Education, Law and Government, Other, Pets and Animals, Politics, Science, Shopping, Sports, Technology, Travel and Transportation, Climate. Empty keeps all. The RSS fallback feed has no categories, so its rows are dropped when this is set. Dropped trends are not saved and not charged.

## `includeArticles` (type: `boolean`):

Up to three news articles per trend with title, URL and source. Google's main feed currently sends none, so they are taken from the country's RSS feed (one more request per country) for the trends it also lists, and other trends get an empty list. Off saves every row without articles.

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

Two-letter language code sent to Google, for example en or pl. The search terms stay in the language of the country.

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

Up to 50 keywords or phrases to check in one run. Used by mode Keyword monitor. In mode Compare keywords it is one group of up to 5 keywords, instead of Comparisons. Leave empty in mode Trending now.

## `comparisons` (type: `array`):

A list of keyword groups, for example \[\["bitcoin", "ethereum"], \["iphone", "pixel"]]. Each group has 1 to 5 keywords, as Google allows, and its keywords share one 0 to 100 scale. Up to 20 groups. A group with more than 5 keywords or an empty group stops the run before any request. Used by mode Compare keywords. To compare one group, you can use Keywords instead.

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

Two-letter country code such as US or PL, or a region such as US-CA. Empty means worldwide.

## `timeframe` (type: `string`):

The window of the interest-over-time series. Values run from 0 to 100 relative to the peak inside this window.

## `property` (type: `string`):

Which Google search the interest is measured in. Web search is the default. A different property is a separate series with its own baseline, so switching it never mixes with the history of the same keyword in web search.

## `monitorName` (type: `string`):

Name of the saved state in the key-value store "google-trends-monitor-state". Runs with the same name are compared with each other. Use a different name for each watchlist. A new name starts a new baseline. Empty means the name "default". The old field name stateKey still works.

## `onlyChanged` (type: `boolean`):

Save only keywords that are new to the state, moved by at least the threshold, or got new rising queries. Unchanged keywords are neither saved nor charged. The first run for a keyword is always saved as its baseline.

## `changeThreshold` (type: `integer`):

How many points (on the 0 to 100 scale) the latest value must move to count as changed. 0 means any move.

## `spikeThreshold` (type: `number`):

A row gets isSpike true when the latest value is at least this many times the median of the 30 days before it (the whole series if it is shorter). 1.5 means 50 percent above the median. Works on the first run too.

## `includeRelatedQueries` (type: `boolean`):

Adds one more Google request per keyword and the top and rising related queries to each row. Needed for new-rising-query alerts. In mode Compare keywords it adds one request per keyword of each group, so turn it off for the fastest comparison.

## `includeRelatedTopics` (type: `boolean`):

Adds up to 10 top and 10 rising related topics (entities such as a company or a person, with their type) to each saved row. One more Google request per saved keyword. Google has returned empty topic lists to sessions that are not logged in, so the lists can be empty.

## `includeRegions` (type: `boolean`):

Adds the sub-regions of the country with the highest interest (for example US states) to each saved row, 10 by default, more with Max regions. Worldwide lists countries. One more Google request per saved keyword. In mode Compare keywords it is one request per group, and the interest is on the shared scale of the group.

## `maxRegions` (type: `integer`):

How many regions to list when Interest by region is on, highest first. Above 10 each entry also gets its rank. Google lists every region that has data, for example 51 US states and territories. Needs Interest by region.

## `includeTimeline` (type: `boolean`):

Adds every point of the interest-over-time series to each row.

## `proxyConfiguration` (type: `object`):

Google answers with a rate limit page when one IP sends too many requests. The actor waits at least 3 seconds between requests and retries with backoff. With the default Apify Proxy, the first CAPTCHA or repeated rate limit moves the run to Apify residential proxy when the account can use it.

## Actor input object example

```json
{
  "mode": "trending",
  "countries": [
    "US"
  ],
  "maxItems": 25,
  "hours": "24",
  "onlyActive": false,
  "minTraffic": 0,
  "includeArticles": true,
  "language": "en",
  "timeframe": "now 7-d",
  "property": "web",
  "onlyChanged": true,
  "changeThreshold": 10,
  "spikeThreshold": 1.5,
  "includeRelatedQueries": true,
  "includeRelatedTopics": false,
  "includeRegions": false,
  "maxRegions": 10,
  "includeTimeline": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `trending` (type: `string`):

No description

## `monitor` (type: `string`):

No description

## `compare` (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 = {
    "countries": [
        "US"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidfetch/google-trends-monitor").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 = {
    "countries": ["US"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("solidfetch/google-trends-monitor").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 '{
  "countries": [
    "US"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call solidfetch/google-trends-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,solidfetch/google-trends-monitor"
        }
    }
}
```

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/EOkvWMEus61laPEWe/builds/ucvrwV238EKCdjdb8/openapi.json
