If you have ever needed Google Trends data inside a script, a scheduled job or an AI agent, you have probably hit the same wall: Google's official Trends API is invite-only, and the approval wait is measured in weeks. Meanwhile pytrends, the unofficial Python library that most people actually used, is archived and increasingly gets blocked.

This post is the working version of "how do I get Trends data into code". It covers what the data really is, three ways to get it, and the limits you should know about before you build on it.

First, the thing people get wrong

Google Trends does not give you search volume.

It gives you relative interest: a number from 0 to 100 describing how much interest there was in a term, where 100 is the highest point within the specific comparison window. That is not "2,400 searches". It is not comparable across unrelated terms, and it is not a demand estimate.

So before you build anything, decide which question you are actually asking:

  • "How many people searched this?" — Trends cannot answer this. Use a search-volume tool. Nothing in this post will help.
  • "Is this going up, and how fast?" — Trends is exactly the right tool, and it is the question most automation is really asking.

Everything below assumes the second question.

The data model you are working with

Once you have Trends data for a term, the useful part is not the raw interest number. It is the rate of change. Four fields do the work:

Field Meaning
trendScore 0-100 composite of 7-day velocity, 30-day velocity and current interest
velocity % change over 7 days
direction rising, falling or flat
breakdown The per-signal score breakdown, so you can see why the score is what it is

The breakdown matters more than it sounds. Most tools hand you a black-box number and ask you to trust it. If your agent is going to act on a score, you want it to be able to see the reasoning, because a score of 76 built on a real 7-day velocity means something quite different from a score of 76 built on a flat week that happened to land on a high-interest day.

There is also a fourth field worth understanding, because it is where a lot of bugs come from: measured: false.

Why "not measured" is not the same as "zero"

Google reports 0 for interest below its sampling threshold. That means a term can sit at 0 for weeks and then jump, and a naive implementation sees "0 now, 50 last week, therefore +infinity" or "0 now, 0 last week, therefore perfectly flat".

Both readings are wrong.

To compute a velocity you need a non-zero baseline. Without one there is no percentage change to compute, so the honest answer is "unmeasurable", not "no change" and not "maximum growth". When that happens the score is reported as unmeasured and the recommendation becomes investigate — investigate by hand, because the data genuinely does not support a decision.

The same distinction applies to the score itself. A composite that redistributes the weight of a missing signal onto the ones that were measured will hand you a confident number built on one real data point. If two of your three signals are unmeasured, the score should be the unmeasured sentinel, not a 33-point floor that looks like a real, mediocre result.

Three ways to get the data into code

1. A plain HTTP API

The simplest option, and the one that works in any language because it is just HTTP.

curl "https://lean-forge.net/api/v1/keywords/trending?limit=5"
{
  "data": [
    {
      "term": "featured snippets",
      "slug": "featured-snippets",
      "category": "seo",
      "trendScore": 76,
      "velocity": 69.23076923076923,
      "direction": "rising",
      "dataAsOf": "2026-11-01T00:00:00.000Z"
    }
  ],
  "_meta": { "basis": "rising" }
}

No signup, no API key, no credit card. The free tier is 100 calls per 15 minutes per IP, which is enough to build against. Endpoints you will want:

GET /api/v1/keywords/trending?limit=20&category=ai
GET /api/v1/keywords/:slug
GET /api/v1/keywords/:slug/next-actions
GET /api/v1/trends/compare?keywords=ChatGPT,Claude,Gemini&geo=US
GET /api/v1/trends/:keywordId/timeline?limit=90
GET /api/v1/categories
GET /api/v1/categories/:slug

JavaScript

const res = await fetch('https://lean-forge.net/api/v1/keywords/trending?limit=10');
if (!res.ok) throw new Error(`trends api: ${res.status}`);

const { data, _meta } = await res.json();

for (const kw of data) {
  console.log(`${kw.term.padEnd(28)} ${String(kw.trendScore).padStart(3)}  ${kw.direction}`);
}

Python

import requests

response = requests.get(
    "https://lean-forge.net/api/v1/keywords/trending",
    params={"limit": 10},
    timeout=10,
)
response.raise_for_status()

for kw in response.json()["data"]:
    print(f"{kw['term']:<28} {kw['trendScore']:>3}  {kw['direction']}")

One keyword, with the reasoning:

curl "https://lean-forge.net/api/v1/keywords/featured-snippets"
{
  "data": {
    "term": "featured snippets",
    "slug": "featured-snippets",
    "trendScore": 76,
    "velocity": 69.23076923076923,
    "direction": "rising",
    "dataAsOf": "2026-11-01T00:00:00.000Z",
    "breakdown": {
      "components": [
        {
          "key": "velocity7d",
          "label": "7-day velocity",
          "rawValue": 69.23076923076923,
          "subScore": 67.3076923076923,
          "weight": 0.5625,
          "contribution": 37.86057692307692,
          "source": "Google Trends",
          "measured": true
        },
        {
          "key": "velocity30d",
          "label": "30-day velocity",
          "rawValue": null,
          "subScore": null,
          "weight": 0,
          "contribution": 0,
          "source": "Google Trends",
          "measured": false
        },
        {
          "key": "currentInterest",
          "label": "Current interest",
          "rawValue": 88,
          "subScore": 88,
          "weight": 0.4375,
          "contribution": 38.5,
          "source": "Google Trends",
          "measured": true
        }
      ],
      "composite": 76.3605769230769,
      "trendScore": 76,
      "measured": true,
      "asOf": "2026-11-01T00:00:00.000Z"
    }
  }
}

Note the 30-day component: rawValue: null, measured: false, weight: 0. That keyword had no usable 30-day baseline, so that signal contributed nothing and its 20% weight was redistributed across the two signals that were measured. The headline score and the breakdown agree, which is the point: the number on the page and the reasoning under it are the same computation, not two computations that happen to look similar.

2. A deterministic next-action verdict

A score is only useful if something acts on it. The next-actions endpoint turns the score into a decision:

curl "https://lean-forge.net/api/v1/keywords/featured-snippets/next-actions"
{
  "data": {
    "verdict": "publish-now",
    "ruleId": "rising-high",
    "rationale": "Rising interest and strong demand — publish SEO content now; peak window.",
    "inputs": {
      "direction": "rising",
      "trendScore": 76,
      "band": "high",
      "category": "seo"
    },
    "related": [],
    "cta": {
      "label": "Compare with rising alternatives",
      "href": "/tools/keyword-comparison-tool?k=featured%20snippets&k=backlink%20analysis&k=SERP%20features"
    }
  }
}

This is a lookup table on (direction × interest band), not a model call. Nine cells, plus one escape hatch for the unmeasured case. The same inputs always produce byte-identical output, and ruleId tells you exactly which rule fired.

That determinism matters more than it sounds for an agent. If your agent asks "should I write about this keyword?" and gets a different answer on Tuesday than it did on Monday, you cannot debug it and you cannot cache it. A rule id can be logged, asserted on and regression-tested.

3. As an MCP tool, so your agent can call it

If you are building an agent, you probably do not want to write the HTTP client inside it at all. There is an MCP server for this:

npm i @leanforge/mcp-server
{
  "mcpServers": {
    "leanforge": {
      "command": "npx",
      "args": ["-y", "@leanforge/mcp-server"]
    }
  }
}

Seven tools: look up one keyword, compare two to five, list what is trending now, browse categories, and track your own private keywords.

The detail that makes it pleasant to use: the read tools work with zero configuration. No API key, no signup, no account, nothing to paste into a config file. The agent can call the tool the moment it is installed.

For hosted agents there is a streamable HTTP transport too:

npx -y @leanforge/mcp-server --http
# POST http://localhost:3000/mcp
# GET  http://localhost:3000/health

If you need the write tools (tracking your own keywords), set LEANFORGE_API_KEY to a key from your account.

A fourth option: doing it yourself

You can still call Google's endpoints directly, and for a one-off script that is a reasonable choice. But be honest about what you are taking on:

  • Google rate-limits and blocks. This is not hypothetical. It is the main reason pytrends stopped being dependable, and it is the reason the service behind the API above polls on a strict, budgeted schedule with a shared daily cap rather than fetching live on every request.
  • The endpoint is undocumented. It can change without notice, and there is no changelog to read when it does.
  • You still have to build the scoring. The raw interest series is not a trend score, a direction or a verdict. All of the judgement described above is on you.

If you are shipping something that other people depend on, this is a problem to solve once, carefully, rather than once per project.

The limits, stated plainly

It is relative interest, not volume. Covered above, and worth repeating because it is the single most common misunderstanding.

A baseline is required. Velocity is only computable against a non-zero baseline. Without one the answer is "unmeasurable", and the API reports it that way rather than guessing.

The source is Google Trends. Sampling, rate limits, and the occasional term that quietly stops being extended all apply. This is a wrapper over a free public data source, not an SLA.

Curated index versus your own terms. The public index is a curated set of 71 terms, refreshed every 6 hours. Tracking your own keywords is a paid feature, because it costs a real upstream fetch per term per day rather than reading from a shared cache.

Free tier versus paid. Reads need no signup and no key. Paid plans raise the rate limit, add API keys, and unlock tracking your own keywords. Starter includes a 14-day trial with no credit card: if you do not add a card when the trial ends, the subscription simply cancels and you are back on the free tier, with nothing charged.

Where to go next

If you have been waiting on Google's approval, you do not have to wait. Try the free tier, and if it does not fit, the trial is 14 days long and needs no card.