On 2026-08-06, "spirit halloween" sat at rank 11 on the live Google Trends board. The same keyword showed +350.0% Google Search growth over 3M, +122.95% Amazon score growth with 21,785 recent volume, and +90.0% YouTube growth. TikTok returned unavailable for that term. This post wires those calls in Python against POST https://api.trendsapi.ai/api, including the double parse of the body string. For the evergreen Google Trends API alternative framing, see Google Trends API alternative.
What this pipeline answers
Seasonal retail teams need an early signal before ads and inventory lock. The question is not "is Halloween popular". The question is whether a specific label is accelerating on search, commerce, and video at the same time. Trends API exposes that as three modes and many sources under one key.
Pricing reminder for capacity planning: free tier 100 successful requests per month, Starter 5,000, Pro 25,000, Business 100,000. Only 200 responses count.
Step 1: discover candidates from the live board
import json
import os
import urllib.request
API = "https://api.trendsapi.ai/api"
KEY = os.environ["TRENDSAPI_API_KEY"]
def trends_api(payload: dict) -> dict:
req = urllib.request.Request(
API,
data=json.dumps(payload).encode(),
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
},
method="POST",
)
envelope = json.loads(urllib.request.urlopen(req).read().decode())
# body is a JSON string; parse it again
return json.loads(envelope["body"])
board = trends_api({
"mode": "get_top_trends",
"type": "Google Trends",
"limit": 15,
})
for rank, label in board["data"]:
print(rank, label)
Live board snapshot (as_of 2026-08-06T00:01:40+00:00), count 15:
| Rank | Label |
|---|---|
| 1 | perez hilton |
| 2 | michigan primary |
| 3 | idaho murders |
| 4 | tom holland spider man movies |
| 5 | glen hansard |
| 11 | spirit halloween |
| 12 | the punisher |
| 13 | samara weaving |
| 14 | lioness |
| 15 | the wall street journal |
Rank 11 is enough to justify a deeper pull. News terms at ranks 1-3 are usually short-lived. A retail label that reappears each August is a better demand-sensing target. Source docs for the Google feed live at Google Trends.
Step 2: measure growth across Google, Amazon, and YouTube
growth = trends_api({
"mode": "get_growth",
"source": "google search, amazon, tiktok, youtube",
"keyword": "spirit halloween",
"percent_growth": ["3M", "6M", "12M"],
})
print("successful", growth["metadata"]["successful_sources"])
print("failed", growth["metadata"]["failed_sources"])
for block in growth["source_results"]:
print(block["source"], block["status"])
for row in block.get("results", []):
vol = row.get("recent_volume")
print(" ", row["period"], row["growth"], row["recent_value"], vol)
Multi-source summary from the 2026-08-06 pull:
- sources_requested: 4
- sources_successful: 3
- sources_failed: 1 (tiktok)
- uptrend_ratio: 3/3 among successful sources
- uptrend_percentage: 100.0
- volume_weighted_growth: 122.07 (Amazon was the only volume-weighted contributor in that rollup)
- total_volume in the weighted block: 21,785.0
Google Search
| Period | Recent | Baseline | Growth | Dates |
|---|---|---|---|---|
| 3M | 9.0 | 2.0 | +350.0% | 2026-08-01 vs 2026-05-02 |
| 6M | 9.0 | 1.0 | +800.0% | 2026-08-01 vs 2026-01-31 |
| 12M | 9.0 | 5.0 | +80.0% | 2026-08-01 vs 2025-08-02 |
Metadata reported 261 data points and 3 successful calculations. Recent value 9.0 is still low on the 0-100 scale. The growth rates are large because the winter baseline was near 1.0. That is normal for seasonal terms. Use the absolute score and the growth together; do not alert on percentage alone.
Amazon
| Period | Recent value | Recent volume | Growth | Volume growth |
|---|---|---|---|---|
| 3M | 13.6 | 21,785 | +122.95% | +122.07% |
| 6M | 13.6 | 21,785 | +189.36% | +187.44% |
| 12M | 13.6 | 21,785 | -3.55% | -4.1% |
Baselines: 6.1 / 9,810 (3M), 4.7 / 7,579 (6M), 14.1 / 22,717 (12M). The 12M row is nearly flat to slightly down versus last August, while 3M and 6M show the climb out of the off-season. Amazon history in this response had 49 points. More Amazon field detail sits on Amazon trends.
YouTube
| Period | Recent | Baseline | Growth |
|---|---|---|---|
| 3M | 57.0 | 30.0 | +90.0% |
| 6M | 57.0 | 20.0 | +185.0% |
| 12M | 57.0 | 64.0 | -10.94% |
YouTube interest is already elevated at 57.0. The 12M decline of -10.94% says last August was slightly hotter on video search. The 3M and 6M climbs say creators are returning now.
TikTok
Status: unavailable. Message: "Data not available for this source." Keep the failed source in logs. A demand job that requires TikTok should try alternate spellings or wait, not invent numbers. Separately, the TikTok Trending Hashtags board on the same day was dominated by "blowthisup", "spiderman", "roblox", "meme", and "tiktokshop". None of those are Spirit Halloween, which matches the missing keyword series.
Step 3: turn the numbers into a cron decision
A simple rule that fits these figures:
- Candidate must appear in the Google Trends top 15 (spirit halloween was 11).
- Google Search 3M growth must be positive (here +350.0%).
- At least one commerce or video source must also be positive on 3M (Amazon +122.95%, YouTube +90.0%).
- If 12M commerce growth is near flat (Amazon -3.55%), treat the move as seasonal re-acceleration, not a new category.
def seasonal_pass(growth_payload: dict) -> bool:
ok_sources = 0
for block in growth_payload["source_results"]:
if block["status"] != "success":
continue
three = next(r for r in block["results"] if r["period"] == "3M")
if three["growth"] > 0:
ok_sources += 1
return ok_sources >= 2
print(seasonal_pass(growth)) # True on the 2026-08-06 pull
Wire that function to Slack, email, or a warehouse table. Poll hourly on free tier only if the keyword list stays tiny; 24 polls × 2 modes × N keywords burns the 100-request free allotment quickly. Daily is enough for seasonal retail.
Cross-check with a non-seasonal control
Control keywords stop false confidence. Crocs on the same day showed mixed commerce signals: Google Search 3M +1.15% (recent 88.0, volume estimate 8,650,000), Google Shopping 3M -6.12% (46.0 vs 49.0), Amazon 3M -6.6% with volume 10,382,410 vs 11,121,531 (-6.65%). Six-month Amazon growth was still strong at +95.39%, but the near-term 3M tape was soft. Spirit Halloween is accelerating into the season. Crocs is a large, already-hot brand cooling slightly on shopping surfaces. The pipeline should emit different alerts for those shapes.
Amazon Best Sellers Top Rated on 2026-08-03 was led by Amazon Basics AA batteries, AAA batteries, and Crocs Classic Clog. That board is a useful second discovery feed when the Google board is news-heavy.
Production notes
- Always parse
envelope["body"]with a secondjson.loads. The outer object is only status plumbing. - Pass comma-separated sources in one
get_growthcall when the plan quota is tight. The spirit halloween call used 4 requested sources and returned per-source blocks. - Store
data_datenext to every row written to the warehouse. These figures are from 2026-08-06. - Link operators to the evergreen alternative page when they ask for a Google Trends API: Google Trends API alternative.
- YouTube source docs: YouTube trends.
Full script skeleton
import json
import os
import urllib.request
API = "https://api.trendsapi.ai/api"
def call(payload):
req = urllib.request.Request(
API,
data=json.dumps(payload).encode(),
headers={
"Authorization": f"Bearer {os.environ['TRENDSAPI_API_KEY']}",
"Content-Type": "application/json",
},
method="POST",
)
env = json.loads(urllib.request.urlopen(req).read().decode())
return json.loads(env["body"])
def main():
board = call({"mode": "get_top_trends", "type": "Google Trends", "limit": 15})
labels = [label for _, label in board["data"]]
if "spirit halloween" not in labels:
print("not on board; skip")
return
growth = call({
"mode": "get_growth",
"source": "google search, amazon, youtube",
"keyword": "spirit halloween",
"percent_growth": ["3M", "6M", "12M"],
})
for block in growth["source_results"]:
if block["status"] != "success":
print(block["source"], "unavailable")
continue
three = next(r for r in block["results"] if r["period"] == "3M")
print(block["source"], three["growth"], three.get("recent_volume"))
if __name__ == "__main__":
main()
Run it once manually, confirm the prints match the tables above within normal daily drift, then schedule it. The useful output is not a chart screenshot. It is a row with rank 11, Google +350.0%, Amazon volume 21,785, YouTube 57.0, and an explicit TikTok miss when that source is requested.