# Google keyword seasonality tracker · 5-Year Weekly (`doesaiknow/keyword-seasonality-breakout-tracker`) Actor

Track how search demand for your keywords moves through the year. One row per keyword: five years of weekly search interest, the peak and trough ISO weeks, year-over-year slope, a z-score breakout alert on the latest closed week, a 13-week outlook, plus monthly search volume and CPC.

- **URL**: https://apify.com/doesaiknow/keyword-seasonality-breakout-tracker.md
- **Developed by:** [David S](https://apify.com/doesaiknow) (community)
- **Categories:** SEO tools, E-commerce, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.012 / seasonality breakout keyword

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/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

## Google Keyword Seasonality Tracker: 5-year weekly seasonality and breakout alerts

Upload a keyword list and get **five years of weekly search seasonality per keyword** — the ISO weeks it
peaks in, the ISO weeks it dies in, whether it is growing year over year, and a **z-score breakout alert**
on the latest closed week — plus a **13-week outlook** and absolute **monthly search volume and CPC** on
the same row.

Built for **SEO leads, category managers and PPC teams planning a calendar**: up to **700 keywords per
run**, one billed row per keyword, no manual chart reading.

### What does the keyword seasonality tracker do?

For every keyword it retrieves the weekly search-interest series for your market and turns it into
decisions: a **seasonal profile by ISO week** (each week of the year averaged across the window and
rescaled so 100 is the keyword's average week), the **top four peak and trough weeks**, a
**year-over-year slope and change**, a **breakout verdict** against the keyword's own baseline, and a
**13-week outlook** with expected searches.

It does **not** hand you a chart to eyeball: every judgement you would make from that chart is already a
column — when to publish, when to stock, when to bid, and whether last week was unusual enough to act on.

### Why use this seasonal keyword trends actor

- **Portfolio, not one phrase at a time.** Up to 700 keywords in a single run, one row each.
- **ISO weeks, not months.** `peak_weeks` names the exact weeks — a category manager buys in week 45, not
  "autumn".
- **Alerts with a threshold you set.** `breakout_flag` is `breakout`, `decline` or `stable`, from the
  z-score of the latest **closed** week against that keyword's own baseline, never against other keywords.
  The current, half-collected week is excluded everywhere, so a collection artefact is never read as a
  collapse.
- **Absolute volume next to the index.** `avg_monthly_searches`, `monthly_searches[]`, `cpc` and
  `competition` sit on the same row, so "seasonal" and "worth it" are answered together.
- **A forward window.** `outlook_13w[]` projects the next 13 ISO weeks and, when volume is known, turns
  them into expected searches — otherwise `null`, never a zero that reads like a measurement.

### How to track keyword seasonality and breakouts

1. Click **Try for free** and open the **Input** tab.
2. Paste your keywords into **Keywords**, one per line, up to 700 per run.
3. Pick the **Market** and **Language** from the lists — you never type a numeric market code.
4. Leave **History window** at 5 years, **Breakout threshold** at 2.0 and **Outlook length** at 13 weeks,
   or tighten the threshold to 1.5 for earlier, noisier alerts.
5. Optionally switch on **Add subregion heat map** — included in the row price, off by default.
6. Click **Start**, then read the **Breakout alerts** view first and **Overview** for the calendar.
7. Add an **Apify Schedule** — weekly matches how fast the latest closed week moves.

```json
{
  "keywords": ["ski jacket", "air fryer", "halloween costumes"],
  "market": "United States",
  "language": "en",
  "windowYears": 5,
  "breakoutZ": 2,
  "outlookWeeks": 13,
  "includeSubregionMap": false
}
```

### What seasonality data you get per keyword

One dataset row per keyword and market, 34 fields. A real row with the long arrays shortened for reading:

```json
{
  "keyword": "ski jacket",
  "keyword_normalized": "ski jacket",
  "market": "United States",
  "language": "English",
  "window_from_week": "2021-W31",
  "window_to_week": "2026-W32",
  "weekly_series": [{ "iso_week": "2026-W31", "week_start": "2026-07-27", "value": 38 }],
  "weeks_covered": 262,
  "seasonality_index": [{ "iso_week": "W01", "index": 93.61, "years_sampled": 5 }],
  "peak_weeks": [{ "iso_week": "W47", "index": 188.91 }, { "iso_week": "W46", "index": 188.06 }],
  "trough_weeks": [{ "iso_week": "W02", "index": 80.12 }, { "iso_week": "W03", "index": 80.12 }],
  "yoy_slope_pct": 0.1,
  "yoy_change_pct": -0.12,
  "baseline_mean": 47.39,
  "baseline_stdev": 16.52,
  "latest_week": "2026-W31",
  "latest_index": 38,
  "latest_zscore": -0.57,
  "breakout_flag": "stable",
  "breakout_threshold_z": 2,
  "outlook_13w": [{ "iso_week": "2026-W33", "expected_index": 80.12, "expected_searches": 13682 }],
  "avg_monthly_searches": 74000,
  "monthly_searches": [{ "year": 2026, "month": 6, "searches": 40500 }, { "year": 2026, "month": 7, "searches": 49500 }],
  "cpc": 0.94,
  "cpc_available": true,
  "competition": "HIGH",
  "competition_index": 87,
  "top_subregions": [{ "subregion": "Colorado", "value": 100 }, { "subregion": "Utah", "value": 88 }],
  "geo_concentration_pct": 18.4,
  "geo_included": true,
  "not_found": false,
  "source": "primary",
  "content_hash": "5525a622eae678fc8f7298ce67ba129a4de6cbf1071d8486b74703e4f99b834d",
  "finished_at": "2026-08-05T09:12:44.031Z"
}
```

| Field | Type | What it is |
|---|---|---|
| `keyword`, `keyword_normalized` | string | Your phrase, and the trimmed lowercased form used for joins |
| `market`, `language` | string | Readable names, never numeric codes |
| `window_from_week`, `window_to_week`, `weeks_covered` | string, integer | Window boundaries as `YYYY-Www`, and how many weekly points arrived |
| `weekly_series[]` | array | `{iso_week, week_start, value}` — value is a 0-100 index, not searches |
| `seasonality_index[]` | array | `{iso_week, index, years_sampled}`, 100 = the keyword's average week |
| `peak_weeks[]`, `trough_weeks[]` | array | Four strongest and four weakest ISO weeks |
| `yoy_slope_pct`, `yoy_change_pct` | number/null | Trend per year, and last 52 weeks vs the 52 before |
| `baseline_mean`, `baseline_stdev`, `latest_week`, `latest_index`, `latest_zscore` | number/string/null | The keyword's own baseline, the latest **closed** ISO week and how unusual it was |
| `breakout_flag`, `breakout_threshold_z` | string/null, number | The verdict, and the threshold that produced it |
| `outlook_13w[]` | array | `{iso_week, expected_index, expected_searches}` for the coming weeks |
| `avg_monthly_searches`, `monthly_searches[]` | integer/array | Absolute volume and up to 12 months of history |
| `cpc`, `cpc_available`, `competition`, `competition_index` | number/bool/string | Advertiser metrics; null = not measured |
| `top_subregions[]`, `geo_concentration_pct`, `geo_included` | array/number/bool | Optional subregion heat map and how regional demand is |
| `not_found`, `source` | boolean, string | No series for this market (a delivered answer), and which path produced the series |
| `content_hash`, `finished_at` | string | Fingerprint of the data, and when the run finished |

### What the interest index is, and what it is not

**`weekly_series[].value` is a relative interest index on a 0-100 scale, normalized by the data source —
not a number of searches.** The same holds for `latest_index`, `baseline_mean`, every `index` field and
the `value` in `top_subregions[]`.

- **Absolute volume lives in `avg_monthly_searches`** and `monthly_searches[]` — those are search counts.
- The index answers **shape** questions (when demand rises, how far last week sat from normal); the volume
  column answers **size** questions. Index values are not comparable across keywords as sizes.
- `expected_searches` is where the two meet: seasonal index times known monthly volume, `null` without it.

### Input parameters

| Input | Type | Default | Notes |
|---|---|---|---|
| `keywords` | array | required | 1-700 keywords, one per line |
| `market` | select | United States | 94 markets by name; a hidden numeric code is available for API runs |
| `language` | select | English | Language of the searches analysed |
| `windowYears` | integer | 5 | Years of weekly history behind the seasonal profile |
| `breakoutZ` | number | 2.0 | Standard deviations from the keyword's own baseline before it is flagged |
| `outlookWeeks` | integer | 13 | How many upcoming ISO weeks to project |
| `includeSubregionMap` | boolean | false | Adds the subregion heat map at no extra charge per row |

Full descriptions live on the **Input** tab. The Actor also runs from the **API** tab, the JavaScript and
Python clients, and through `mcp.apify.com`.

### How much does the keyword seasonality tracker cost?

Pay per event — you pay for answers, not for runtime.

| Event | When it fires | Price |
|---|---|---|
| Actor start | Once per run | $0.005 |
| Seasonality breakout keyword | Once per delivered row | $0.018 or lower on higher plans |

- A keyword with **no series is a delivered row and is billed** — "no measurable history in this market"
  is an answer, and it stops you planning around a keyword nobody searches.
- A keyword whose data could **not** be retrieved is **not delivered and not billed**, and repeating the
  same keyword and settings **within 24 hours re-delivers the cached row unbilled**.
- The subregion heat map has **no event of its own** — a row that carries a map and a row that does not cost exactly the same.

### Keyword seasonality tracker vs trends UI and SEO suites

| | This Actor | Free trends web UI | SEO suites |
|---|---|---|---|
| Input size | 700 keywords per run | One phrase at a time | Keyword lists, monthly plan |
| Seasonal profile | ISO-week index, 5 years pooled | A chart to read yourself | Monthly volume history |
| Breakout alert | z-score verdict with your threshold | None | Sometimes, as a trend arrow |
| Forward outlook | 13 weeks, with expected searches | None | Rarely |
| Absolute volume | On the same row | Not available | Yes |
| Output and commitment | Dataset/API, per delivered row | Screenshots, manual work | Dashboard, monthly plan |

### What this actor does not do

- It does **not** return absolute search volume in the weekly series. The series is a normalized 0-100
  index; the volume column is `avg_monthly_searches`, and the two answer different questions.
- It does **not** report the current, partial ISO week. Alerts and baselines use closed weeks only, so the
  freshest verdict is about last week, not today.
- It does **not** forecast demand shocks. `outlook_13w[]` projects the keyword's own seasonal shape and
  knows nothing about a launch, a news cycle or a competitor's campaign.
- It does **not** invent data it does not have. Missing volume is `null` with `expected_searches: null`,
  an unmeasurable verdict is `breakout_flag: null`, and unspecified competition stays `null`, never "LOW".
- It does **not** guarantee five full years per phrase: a young keyword returns fewer `weeks_covered`.
- It does **not** cover more than one market per run, caps the subregion map at 10 subregions per keyword,
  and never logs into an account, drives a browser or bypasses any protection.

### FAQ

#### How do I find out when a keyword peaks during the year?

Read `peak_weeks[]` — the four strongest ISO weeks of the five-year seasonal profile — and
`seasonality_index[]` for the full 52-week shape. Week labels carry no year because the profile pools
years; plan against the week number.

#### What exactly is a breakout here?

The latest **closed** week is compared with that keyword's own baseline mean and standard deviation. More
than `breakoutZ` deviations above is `breakout`, the same below is `decline`, inside the band is `stable`.

#### Why is `latest_zscore` null on some rows?

Because the baseline has no spread to measure against: a keyword whose closed weeks are flat has a
standard deviation of 0, so no z-score exists. The verdict is `null` too, deliberately not `stable` — an
unmeasurable week is not a calm week.

#### Is this the same as reading a free trends chart?

The underlying data is the same kind, but the chart is one phrase at a time and exports nothing. This
Actor takes 700 phrases per run, converts each into an ISO-week index, adds a breakout verdict, a 13-week
outlook and absolute volume, and returns a dataset.

#### How often should I re-run it?

Weekly — the verdict is about the latest closed ISO week. Re-running a keyword inside 24 hours returns
the cached row unbilled.

### Related actors

- [AI Overview Disruption Monitor](https://apify.com/doesaiknow/ai-overview-disruption-monitor) — whether
  the keywords you are planning around still send clicks.
- [Amazon Listing Optimizer](https://apify.com/doesaiknow/amazon-listing-optimizer) — turn a peak week
  into a listing that is ready for it.

### Legal & support

- This Actor returns **aggregated search-interest and keyword planning metrics**. It collects no personal
  data and returns no end-user identities or individual search histories.
- Data is retrieved through a licensed data **API** — no login, no account takeover, no anti-bot
  circumvention, no automated browsing of search engines.
- Output is a **point-in-time observation** of aggregated demand, provided as-is for planning. The
  13-week outlook projects past seasonality, is not a guarantee of future demand, and is not financial
  advice.
- The weekly series is a **normalized 0-100 index published by the source**, not a count of searches;
  absolute volume is reported separately in `avg_monthly_searches`. Coverage limits are stated in the row
  itself — `weeks_covered`, `not_found`, `cpc_available` and `geo_included`.
- You are responsible for using the output in line with the law in your jurisdiction and the terms of the
  platforms you advertise on. Google is a trademark of Google LLC; this Actor is not affiliated with or
  endorsed by Google.
- Questions or a bug? Open an issue on the **Issues** tab; see **API** for integration.

### SEO keywords

keyword seasonality tool, seasonal keyword trends, google trends api, search trend alerts, demand
forecasting tool, keyword trend tracker, search demand seasonality, seasonal search volume, keyword
breakout detection, bulk keyword trends, weekly search interest data, seasonal SEO planning, content
calendar keyword research, ecommerce demand planning, peak season keyword research, year over year
keyword trends, keyword trend monitoring, seasonal demand analysis.

Built by doesaiknow.

# Actor input Schema

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

Keywords to track, one per line (up to 700 per run). Every keyword produces exactly one result row for the selected market — the seasonal calendar, the breakout alert and the 13-week outlook are arrays inside that same row.

## `market` (type: `string`):

Country the search interest is measured in. Pick a name from the list — you never type a numeric market code.

## `location_code` (type: `integer`):

API only (hidden in the UI): numeric market code for programmatic runs. The Market field above always wins when both are set.

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

Language of the searches analysed in the selected market.

## `windowYears` (type: `integer`):

How many years of weekly history to analyse. The seasonal index averages each ISO week across the years in this window.

## `breakoutZ` (type: `number`):

How far the latest closed ISO week must sit from its own baseline before it is flagged as a breakout or a decline. 2.0 means two standard deviations.

## `outlookWeeks` (type: `integer`):

How many upcoming ISO weeks to project from the seasonal index.

## `includeSubregionMap` (type: `boolean`):

Off by default. When on, each row also carries the top subregions for the keyword and how concentrated the demand is, at no extra charge per row.

## Actor input object example

```json
{
  "keywords": [
    "air fryer",
    "ski jacket",
    "halloween costumes"
  ],
  "market": "United States",
  "language": "en",
  "windowYears": 5,
  "breakoutZ": 2,
  "outlookWeeks": 13,
  "includeSubregionMap": false
}
```

# Actor output Schema

## `results` (type: `string`):

All delivered rows with the weekly series, seasonal index, peak and trough weeks, breakout verdict, outlook, volume and CPC.

## `overview` (type: `string`):

Compact view: verdict, z-score, peak ISO weeks, year-over-year slope and monthly volume.

## `breakouts` (type: `string`):

Alert view: only the keywords whose latest closed week broke out above their own baseline, with the threshold used.

## `cache` (type: `string`):

Delivered rows kept for 24 hours. Re-running the same keyword, market and settings inside that window re-delivers the row and is not billed.

# 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": [
        "air fryer",
        "ski jacket",
        "halloween costumes"
    ],
    "market": "United States",
    "windowYears": 5,
    "breakoutZ": 2,
    "outlookWeeks": 13
};

// Run the Actor and wait for it to finish
const run = await client.actor("doesaiknow/keyword-seasonality-breakout-tracker").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": [
        "air fryer",
        "ski jacket",
        "halloween costumes",
    ],
    "market": "United States",
    "windowYears": 5,
    "breakoutZ": 2,
    "outlookWeeks": 13,
}

# Run the Actor and wait for it to finish
run = client.actor("doesaiknow/keyword-seasonality-breakout-tracker").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": [
    "air fryer",
    "ski jacket",
    "halloween costumes"
  ],
  "market": "United States",
  "windowYears": 5,
  "breakoutZ": 2,
  "outlookWeeks": 13
}' |
apify call doesaiknow/keyword-seasonality-breakout-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,doesaiknow/keyword-seasonality-breakout-tracker"
        }
    }
}

```

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/sBo4vgDCgMbLWaDmU/builds/1nvhl5vtDKPXv61Vp/openapi.json
