> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Term → Content Strategy

> Pull top search terms from Google Ads and cluster them by intent with Mave Agent to build a data-driven content calendar.

### Scenario

Your Google Ads account has thousands of actual search queries that triggered your ads — real language from real buyers. Instead of guessing what content to write, you pull the top 100 search terms by impressions, send them to Mave with the instruction to cluster by intent, and get back a content strategy mapped to how people actually search. The result is a content calendar grounded in paid search data.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Google Ads GAQL (search_term_view)"] --> B[Top 100 terms by impressions] --> C["POST /api/v1/mave/chat"] --> D[Intent clusters + content plan]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests
  from google.ads.googleads.client import GoogleAdsClient

  MV = os.environ["MAVERA_API_KEY"]
  CUSTOMER_ID = os.environ["GOOGLE_ADS_CUSTOMER_ID"]

  client = GoogleAdsClient.load_from_env()
  ga_service = client.get_service("GoogleAdsService")

  query = """
      SELECT
          search_term_view.search_term,
          metrics.impressions,
          metrics.clicks,
          metrics.conversions,
          metrics.cost_micros
      FROM search_term_view
      WHERE segments.date DURING LAST_30_DAYS
          AND metrics.impressions > 10
      ORDER BY metrics.impressions DESC
      LIMIT 100
  """

  response = ga_service.search(customer_id=CUSTOMER_ID, query=query)

  terms = []
  for row in response:
      stv = row.search_term_view
      m = row.metrics
      terms.append({
          "term": stv.search_term,
          "impressions": m.impressions,
          "clicks": m.clicks,
          "conversions": m.conversions,
          "cost": m.cost_micros / 1_000_000,
      })

  term_block = "\n".join(
      f"- \"{t['term']}\" (imp: {t['impressions']}, clicks: {t['clicks']}, conv: {t['conversions']:.0f}, cost: ${t['cost']:.2f})"
      for t in terms
  )

  mave = requests.post(
      "https://app.mavera.io/api/v1/mave/chat",
      headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
      json={"message": f"""Cluster these {len(terms)} Google Ads search terms by user intent.

  For each cluster:
  1. Cluster name and intent type (informational, navigational, commercial, transactional)
  2. Representative terms
  3. Total impressions and conversions for the cluster
  4. Recommended content type (blog post, landing page, comparison guide, tutorial, etc.)
  5. Suggested title and angle

  Prioritize clusters by conversion potential.

  SEARCH TERMS:
  {term_block}"""},
  ).json()

  print("--- Search Term Content Strategy ---")
  print(mave.get("content", "")[:3000])
  print(f"\nSources: {len(mave.get('sources', []))}")
  ```

  ```javascript JavaScript theme={"dark"}
  const DEV_TOKEN = process.env.GOOGLE_ADS_DEVELOPER_TOKEN;
  const ACCESS_TOKEN = process.env.GOOGLE_ADS_ACCESS_TOKEN;
  const CUSTOMER_ID = process.env.GOOGLE_ADS_CUSTOMER_ID;
  const MV = process.env.MAVERA_API_KEY;

  const gaql = `
    SELECT
      search_term_view.search_term,
      metrics.impressions,
      metrics.clicks,
      metrics.conversions,
      metrics.cost_micros
    FROM search_term_view
    WHERE segments.date DURING LAST_30_DAYS
      AND metrics.impressions > 10
    ORDER BY metrics.impressions DESC
    LIMIT 100`;

  const gaRes = await fetch(
    `https://googleads.googleapis.com/v23/customers/${CUSTOMER_ID}/googleAds:searchStream`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${ACCESS_TOKEN}`,
        "developer-token": DEV_TOKEN,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: gaql }),
    }
  ).then((r) => r.json());

  const terms = (gaRes[0]?.results || []).map((row) => ({
    term: row.searchTermView.searchTerm,
    impressions: parseInt(row.metrics.impressions),
    clicks: parseInt(row.metrics.clicks),
    conversions: parseFloat(row.metrics.conversions),
    cost: parseInt(row.metrics.costMicros) / 1_000_000,
  }));

  const termBlock = terms
    .map((t) => `- "${t.term}" (imp: ${t.impressions}, clicks: ${t.clicks}, conv: ${t.conversions.toFixed(0)}, cost: $${t.cost.toFixed(2)})`)
    .join("\n");

  const mave = await fetch("https://app.mavera.io/api/v1/mave/chat", {
    method: "POST",
    headers: { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      message: `Cluster these ${terms.length} Google Ads search terms by user intent.\n\nFor each cluster:\n1. Cluster name and intent type\n2. Representative terms\n3. Total impressions and conversions\n4. Recommended content type\n5. Suggested title and angle\n\nSEARCH TERMS:\n${termBlock}`,
    }),
  }).then((r) => r.json());

  console.log("--- Search Term Content Strategy ---");
  console.log((mave.content || "").slice(0, 3000));
  console.log(`\nSources: ${(mave.sources || []).length}`);
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
--- Search Term Content Strategy ---

## Cluster 1: Evaluation / Comparison (Transactional)
Terms: "best crm for startups", "hubspot vs salesforce pricing", "crm comparison 2026"
Impressions: 8,420 | Conversions: 34
→ Content: Comparison landing page
→ Title: "CRM Comparison Guide: Features, Pricing, and Fit for Growing Teams"

## Cluster 2: How-To / Implementation (Informational)
Terms: "how to set up crm pipeline", "crm automation examples", "lead scoring setup"
Impressions: 12,300 | Conversions: 12
→ Content: Tutorial blog series
→ Title: "How to Build a CRM Pipeline That Actually Converts"

## Cluster 3: Problem-Aware (Commercial)
Terms: "sales team losing deals", "pipeline visibility tool", "forecast accuracy software"
Impressions: 5,100 | Conversions: 28
→ Content: Problem/solution landing page
→ Title: "Why Your Sales Team Keeps Losing Deals (and How to Fix It)"

Sources: 3
```

### Error Handling

<AccordionGroup>
  <Accordion title="GAQL query errors">Invalid field names or date ranges return `QUERY_ERROR`. Test queries in the [Google Ads Query Builder](https://developers.google.com/google-ads/api/fields/v23/overview_query_builder).</Accordion>
  <Accordion title="No search term data">New campaigns or Smart campaigns may not expose search terms. Ensure campaigns use manual or broad-match keywords with search term reporting enabled.</Accordion>
  <Accordion title="Customer ID format">Pass the 10-digit ID without dashes. The Python client strips dashes automatically; the REST API does not.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="All Google Ads jobs" icon="rectangle-history" href="/integrations/google-ads">
    View all 7 Google Ads integration jobs
  </Card>

  <Card title="Mave Agent" icon="brain" href="/api-reference/mave">
    Full reference for POST /api/v1/mave/chat
  </Card>
</CardGroup>
