---
title: "Build a Market Research Agent with Search and Trends Data"
description: "Wire Google Search, Trends and News into one Python agent, cap it at 8 credits per question, and see a recorded Trends response."
url: https://perceptlayer.com/blog/market-research-agent-search-trends
---

[Blog](https://perceptlayer.com/blog) / [AI agents](https://perceptlayer.com/blog/category/ai-agents)

# Build a Market Research Agent with Search and Trends Data

23 Oct 2026 Perceptlayer 8 min read

An AI market research agent needs three kinds of evidence: what the web says now (search results), whether interest is rising or falling (trends), and what was published this week (news). You can give an agent all three as tools backed by the Perceptlayer Google Search, Google Trends and Google News endpoints, each costing 1 credit per successful call. This tutorial builds that agent in Python with a hard credit cap per question.

## Key takeaways

-   Three endpoints cover a research question: `/v1/google-search/search`, `/v1/google-trends/interest` (plus `/v1/google-trends/related`) and `/v1/google-news/news`, 1 credit each.
-   Trim every response before it reaches the model. The recorded search response carries fields like `redirect_link` that an agent doesn't need.
-   A budget check in your own code, reading `X-Credits-Used`, caps spend per question. With a cap of 8 credits, the 1,000 free monthly credits cover 125 questions.
-   Trends values are relative on a 0 to 100 scale, so the agent should report direction and peaks, never search volume.

## What should a market research agent actually call?

A research question such as "Is demand for standing desks growing, and who is talking about it?" splits into three lookups. Search shows who ranks and what questions people ask. Trends shows whether interest is rising over a period you choose. News shows what happened recently. One tool each keeps the agent's choices simple and its costs predictable.

| Tool | Endpoint | Answers | Credits |
| --- | --- | --- | --- |
| `search_web` | `GET /v1/google-search/search` | Who ranks, related searches, related questions | 1 |
| `search_trends` | `GET /v1/google-trends/interest` or `/related` | Is interest rising, what else people search | 1 |
| `search_news` | `GET /v1/google-news/news` | What was published recently, by whom | 1 |

The tools map to the [Google Search API](https://perceptlayer.com/apis/google-search), the [Google Trends API](https://perceptlayer.com/apis/google-trends) and the [Google News API](https://perceptlayer.com/apis/google-news). If you want a deeper look at one of them, see [Google Search API: Get Search Results as JSON](https://perceptlayer.com/blog/google-search-api-json) and [Google Trends API in Python: Interest Over Time as JSON](https://perceptlayer.com/blog/google-trends-api-python).

## Step 1: How do you test each endpoint before wiring the agent?

Run each call once with cURL, so you see the response shape before the model does. You need a key: [sign up](https://perceptlayer.com/signup) for 1,000 free credits a month, no credit card, and send it in the `X-API-Key` header ( [authentication](https://docs.perceptlayer.com/authentication)). Failed calls cost nothing and return `X-Credits-Used: 0`.

```bash
export API_KEY=sk_live_...

curl "https://api.perceptlayer.com/v1/google-search/search?query=standing+desks&hl=en" \
  -H "X-API-Key: $API_KEY"

curl "https://api.perceptlayer.com/v1/google-trends/interest?q=standing+desk&geo=US&time_range=1y" \
  -H "X-API-Key: $API_KEY"

curl "https://api.perceptlayer.com/v1/google-news/news?q=standing+desk" \
  -H "X-API-Key: $API_KEY"
```

The Trends call is the one that surprises people, so here is an excerpt of a recorded response for `q=tesla&geo=US&time_range=1y`:

```json
{
  "search_parameters": {
    "keyword": "tesla",
    "geo": "US",
    "time_range": "today+12-m",
    …
  },
  "interest_over_time": {
    "average": 49.13,
    "max_value": 100,
    "min_value": 39,
    "data_points": [
      { "timestamp": 1760832000, "date": "Oct 19 – 25, 2025", "value": 40 },
      { "timestamp": 1761436800, "date": "Oct 26 – Nov 1, 2025", "value": 39 },
      …
    ]
  }
}
```

Each point is a week with a `value`. The peak week is 100 by definition, because Google scales the series. Per [Google's Trends documentation](https://support.google.com/trends/answer/4365533) (2026), figures are "scaled on a range of 0 to 100 based on a topic's proportion to all searches on all topics", so they show relative interest, not absolute search volume. Put that in your agent's system prompt.

## Step 2: How do you define the three tools?

Define each tool with a name, a description and a JSON schema, then map it to one endpoint and a fixed set of parameters. Keeping the schema small matters: the search endpoint accepts dozens of optional parameters, and an agent that can set all of them will spend credits exploring. Expose only `query`, a region and, for Trends, a time range.

```python
import json
import os
import time

import anthropic
import requests

API = "https://api.perceptlayer.com"
KEY = os.environ["API_KEY"]
MODEL = "claude-sonnet-5-5"
BUDGET = 8  # credits per research question
MAX_TURNS = 10  # model round trips per question

TOOLS = [
    {
        "name": "search_web",
        "description": "Google search results for a query: top organic results, "
        "related searches and related questions. Costs 1 credit.",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "search_trends",
        "description": "Google Trends for one keyword. view=interest returns "
        "relative interest over time (0 to 100, not search volume). "
        "view=related returns related queries. Costs 1 credit.",
        "input_schema": {
            "type": "object",
            "properties": {
                "keyword": {"type": "string"},
                "view": {"type": "string", "enum": ["interest", "related"]},
                "geo": {"type": "string", "description": "Country code, e.g. US"},
                "time_range": {
                    "type": "string",
                    "enum": ["30d", "90d", "1y", "5y"],
                },
            },
            "required": ["keyword", "view"],
        },
    },
    {
        "name": "search_news",
        "description": "Recent Google News articles for a query. Costs 1 credit.",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
]
```

The tool schema format follows [Anthropic's tool use documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use). The same three tools work with other model providers' function calling with small changes; [MCP vs Function Calling for Live Web Data in Agents](https://perceptlayer.com/blog/mcp-vs-function-calling-web-data) covers when each fits.

## Step 3: How do you trim responses before the model sees them?

Return only the fields the agent will reason about. Raw search responses include tracking links, pagination URLs and filter links that cost tokens and add nothing to an analysis. The recorded search response, for example, carries a long `redirect_link` beside each result's `link`. The trimmer below keeps fields that appear in the recorded responses.

```python
def shrink(name, data):
    if name == "search_web":
        return {
            "organic": [
                {k: r.get(k) for k in ("position", "title", "link", "snippet", "source")}
                for r in data.get("organic_results", [])[:5]
            ],
            "related_searches": [r["query"] for r in data.get("related_searches", [])],
            "related_questions": [q["question"] for q in data.get("related_questions", [])],
        }
    if name == "search_news":
        return [
            {
                "title": n.get("title"),
                "link": n.get("link"),
                "snippet": n.get("snippet"),
                "iso_date": n.get("iso_date"),
                "source": (n.get("source") or {}).get("name"),
            }
            for n in data.get("news_results", [])[:5]
        ]
    if name == "search_trends" and "interest_over_time" in data:
        iot = data["interest_over_time"]
        return {
            "average": iot.get("average"),
            "max_value": iot.get("max_value"),
            "min_value": iot.get("min_value"),
            "points": [
                [p.get("date"), p.get("value")] for p in iot.get("data_points", [])
            ],
        }
    return json.dumps(data)[:3000]  # related queries: pass through, capped
```

For the recorded Trends response above, `shrink("search_trends", data)` returns this (excerpt, trimmed with …):

```json
{
  "average": 49.13,
  "max_value": 100,
  "min_value": 39,
  "points": [
    ["Oct 19 – 25, 2025", 40],
    ["Oct 26 – Nov 1, 2025", 39],
    …
  ]
}
```

The `search_parameters`, the duplicate `timeline_data` list, the per-point `timestamp` and `response_time_ms` are gone. What is left is the summary and the series.

On the Trends tool, the `points` list is the full series as `[date, value]` pairs. For a one-year range that is a compact table the model can read for direction and peaks. The `related` view has no recorded sample in this article, so it is passed through as text with a character cap. Check its shape in the [playground on the Trends API page](https://perceptlayer.com/apis/google-trends/related) before you rely on specific fields.

## Step 4: How do you cap credits per question?

Count credits in your own code and stop calling tools when the cap is reached. Every successful response carries `X-Credits-Used` and `X-Credits-Remaining`, so the gateway tells you the true cost of each call. Add the used credits to a running total, refuse further calls once it hits `BUDGET`, and hand the model a plain error so it writes its answer from what it has ( [credits](https://docs.perceptlayer.com/credits)).

```python
class Spend:
    credits = 0

def call(path, params):
    if Spend.credits >= BUDGET:
        return {"error": "budget_reached", "message": "No credits left for this question. Answer from the data you have."}
    for attempt in range(2):
        try:
            r = requests.get(
                API + path, params=params, headers={"X-API-Key": KEY}, timeout=30
            )
        except requests.Timeout:
            # A lost reply may have succeeded and been billed. Don't retry blindly.
            return {"error": "timeout", "message": "The request timed out."}
        Spend.credits += int(r.headers.get("X-Credits-Used", 0))
        if r.status_code in (429, 503) and attempt == 0:
            time.sleep(int(r.headers.get("Retry-After", 1)))
            continue
        if r.status_code != 200:
            return r.json()  # {"error": {"code", "message", "request_id"}}
        return r.json()

def run_tool(name, args):
    if name == "search_web":
        data = call("/v1/google-search/search", {"query": args["query"], "hl": "en"})
    elif name == "search_news":
        data = call("/v1/google-news/news", {"q": args["query"], "hl": "en"})
    else:
        params = {
            "q": args["keyword"],
            "geo": args.get("geo", "US"),
            "time_range": args.get("time_range", "1y"),
        }
        data = call("/v1/google-trends/" + args["view"], params)
    if "error" in data:
        return json.dumps(data)
    out = shrink(name, data)
    return out if isinstance(out, str) else json.dumps(out)
```

Two details matter here. A 429 or 503 carries `Retry-After`, so the code waits that many seconds and retries once; those calls are not debited. A client timeout is different: requests have no idempotency key, so a timed-out call may have been billed, and the code reports it instead of retrying. [Handle Web Data API Errors in Python](https://perceptlayer.com/blog/handle-web-data-api-errors-python) goes deeper on timeouts and lists the error codes.

## Step 5: How do you run the agent loop?

Send the question, execute any tool calls the model requests, return the results and repeat until the model answers in text. The system prompt sets the rules: use Trends for direction, search for who ranks, news for recency, cite links, and never state absolute search volume. Reset the spend counter for each question, and bound the loop with a turn limit.

```python
SYSTEM = (
    "You are a market research analyst. Use search_trends to judge whether "
    "interest is rising or falling, search_web to see who ranks and what people "
    "ask, and search_news for what happened recently. Trends values are relative "
    "(0 to 100), never search volume. You have a budget of "
    f"{BUDGET} tool calls. Finish with: a one-paragraph answer, then evidence "
    "bullets with source links. Say what the data cannot show."
)

client = anthropic.Anthropic()

def research(question):
    Spend.credits = 0
    messages = [{"role": "user", "content": question}]
    for turn in range(MAX_TURNS):
        # On the last turn, forbid tools so the model must answer in text.
        choice = {"type": "none"} if turn == MAX_TURNS - 1 else {"type": "auto"}
        resp = client.messages.create(
            model=MODEL, max_tokens=2000, system=SYSTEM,
            tools=TOOLS, tool_choice=choice, messages=messages,
        )
        messages.append({"role": "assistant", "content": resp.content})
        if resp.stop_reason != "tool_use":
            text = "".join(b.text for b in resp.content if b.type == "text")
            return text, Spend.credits
        results = [
            {
                "type": "tool_result",
                "tool_use_id": b.id,
                "content": run_tool(b.name, b.input),
            }
            for b in resp.content
            if b.type == "tool_use"
        ]
        messages.append({"role": "user", "content": results})
    return "Stopped: turn limit reached.", Spend.credits

if __name__ == "__main__":
    answer, spent = research(
        "Is demand for standing desks growing in the US, and who is covering it?"
    )
    print(answer)
    print(f"Credits spent: {spent}")
```

Run it with `python agent.py`. The script prints the answer and the credits it spent, which will be 8 or fewer. How many tools the model calls is its own choice. Two counters set the ceilings: the credit cap in `call` stops spend, and `MAX_TURNS` stops the loop, because after `budget_reached` a model can keep requesting tools and burn tokens without spending credits. The final turn sets `tool_choice` to `none`, so the model has to write its answer. The system prompt fixes the shape of what you get back: a one-paragraph answer, evidence bullets with source links, and a note on what the data cannot show. I haven't recorded a full run of this agent for this article, so the output depends on the live data and the model you use.

## What does one research question cost?

A question costs the number of tool calls, at 1 credit each, up to your cap. A plan with one search, one Trends interest call, one related call and one news call is 4 credits. A cap of 8 allows a second round of follow-ups. Calls that fail cost nothing, so retries after a 429 don't add to the total.

| Scenario | Calls | Credits |
| --- | --- | --- |
| Minimum plan (search, interest, news) | 3 | 3 |
| Standard plan (adds `related`) | 4 | 4 |
| Capped worst case | 8 | 8 |

At the cap, 125 questions × 8 credits = 1,000 credits, which is the free monthly allowance. At the standard plan, 250 questions × 4 credits = 1,000 credits. For a weekly sweep of 200 keywords at 4 credits each, 200 × 4 = 800 credits a week. For what credits cost in packs, see [pricing](https://perceptlayer.com/pricing). [AI Agent API Cost Control: Cap Web Data Spend](https://perceptlayer.com/blog/ai-agent-web-data-costs) covers account-level limits, and [Cache API Responses in Python](https://perceptlayer.com/blog/cache-web-data-api-responses) shows how to avoid paying twice for a Trends series that changes weekly.

## How should you read the results?

Treat the agent's output as a lead for a human analyst, not a finding. Trends shows relative interest in search terms, so a rising line for "standing desk" means more people search it relative to all searches, not that sales rose. Search results and news are returned as found at the time of the call, and rankings vary by location and device.

A few habits keep the answers honest. Keep geography and time range the same when you compare readings, because Google divides each data point "by the total searches of the geography and time range it represents" ( [Google Trends Help](https://support.google.com/trends/answer/4365533), 2026). Prefer `time_range=5y` when the question is about seasonality, since a 90-day window can mistake a seasonal peak for growth. And ask the agent to name the evidence behind each claim. For forecasting use cases, [Search Trend Data for Demand Forecasting: What It Can Show](https://perceptlayer.com/blog/search-trend-data-demand-forecasting) lays out the limits in more detail.

## What are the limits of this design?

The design is a research assistant, not a market-sizing model. It sees public search interest and news, so it can't tell you revenue, conversion or what customers pay. Free accounts get 1 request a second (60 a minute), which a single sequential agent stays under; parallel agents on one account share that rate ( [rate limits](https://docs.perceptlayer.com/rate-limits)).

If you want the same tools without writing the HTTP layer, the MCP server at `https://api.perceptlayer.com/mcp` exposes `search_endpoints`, `get_endpoint` and `call_endpoint`. The first two only look up the catalogue and cost nothing, and `call_endpoint` spends the endpoint's credits on success only. See [AI agents (MCP)](https://docs.perceptlayer.com/ai-agents) and [Claude MCP Web Search](https://perceptlayer.com/blog/claude-agent-live-search-mcp). The trade-off: with MCP the model picks endpoints from the whole catalogue, so a code-level cap like the one above matters even more.

Next step: [get a free API key](https://perceptlayer.com/signup), run the three cURL calls from Step 1, then paste the script into `agent.py` and ask it your own research question.

## APIs in this post

-   [Google Search **1 credit per call** · 7 endpoints](https://perceptlayer.com/apis/google-search)
-   [Google Trends **1 credit per call** · 6 endpoints](https://perceptlayer.com/apis/google-trends)
-   [Google News **1 credit per call** · 1 endpoint](https://perceptlayer.com/apis/google-news)

## Frequently asked questions

### How many credits does a market research agent use per question?

Each successful call to the search, Trends or News endpoint costs 1 credit, so a question costs one credit per tool call. A typical plan of one search, two Trends calls and one news call is 4 credits. Capping the agent at 8 calls bounds the worst case at 8 credits. Failed calls cost nothing.

### Does Google Trends return search volume?

No. Google scales Trends data from 0 to 100, based on a topic's proportion to all searches, so it shows relative interest rather than absolute volume ( [Google Trends Help](https://support.google.com/trends/answer/4365533), 2026). The recorded response shows an `average`, a `max_value` of 100 and weekly `value` points. Use it for direction and timing.

### Can I use MCP instead of defining the tools myself?

Yes. The MCP server at `https://api.perceptlayer.com/mcp` has `search_endpoints`, `get_endpoint` and `call_endpoint`, so an agent can find and call the same three APIs. Lookups are free and calls cost the endpoint's credits. You lose the fixed parameter schemas used here, so enforce the credit cap on your side.

### What happens if my agent runs out of credits?

The service is prepaid. When the balance reaches zero, calls return `insufficient_credits` (402) and nothing more is debited; there is no automatic top-up. Your code gets a JSON error with a `code`, `message` and `request_id`, which the agent loop above passes to the model as a tool result. See [errors](https://docs.perceptlayer.com/errors).

### Can the agent run searches in parallel?

It can, but requests are rate limited per account and counted per minute. Free accounts get 1 request a second; paid packs include higher rates, and at least 5 a second for paying accounts. Parallel tool calls from one agent share that budget. If you hit a 429, wait the `Retry-After` seconds and retry.

ai agents market research google trends tool calling python

## More from the blog

1.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 22 Jul 2026 Perceptlayer 11 min read
    
    ## [LangChain Custom Tool for Live Web Data: Python Tutorial](https://perceptlayer.com/blog/langchain-custom-tool-web-data)
    
    Build a LangChain tool in Python for Google Search and News: typed arguments, retries on 429, 502 and 503 only, and a per-run credit cap.
    
2.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 16 Jul 2026 Perceptlayer 7 min read
    
    ## [AI Shopping Agent: Compare Prices Across Marketplaces](https://perceptlayer.com/blog/ai-shopping-agent-compare-prices)
    
    A Python AI shopping agent that searches Google Shopping, Walmart and AliExpress, normalises offers into one table and stops at a credit cap of 6 per question.
    
3.  [AI agents](https://perceptlayer.com/blog/category/ai-agents) 12 Oct 2026 Perceptlayer 7 min read
    
    ## [MCP Server Too Many Tools: Keeping Agent Context Small](https://perceptlayer.com/blog/mcp-too-many-tools-context)
    
    One tool per endpoint for 1,659 endpoints is about 567,000 characters of schema. A 10-step checklist to keep an MCP server's tool list small.
