> ## 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.

# Display/YouTube Placement Analysis

> Analyze top Display and YouTube placements from Google Ads with Mave Agent to discover converting content themes and wasted spend.

### Scenario

Your Display and YouTube campaigns run across thousands of placements — websites, YouTube channels, apps. Most spend is wasted on irrelevant sites. You pull the top-performing placements, send them to Mave for content theme analysis, and discover which content environments drive conversions. The output informs placement targeting, exclusion lists, and content partnerships.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["Google Ads GAQL (group_placement_view)"] --> B[Top placements by conversions] --> C["POST /api/v1/mave/chat"] --> D[Content theme analysis]
```

### 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
          group_placement_view.display_name,
          group_placement_view.target_url,
          group_placement_view.placement_type,
          metrics.impressions,
          metrics.clicks,
          metrics.conversions,
          metrics.cost_micros,
          metrics.ctr
      FROM group_placement_view
      WHERE segments.date DURING LAST_30_DAYS
          AND metrics.impressions > 100
      ORDER BY metrics.conversions DESC
      LIMIT 50
  """

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

  placements = []
  for row in response:
      gpv = row.group_placement_view
      m = row.metrics
      placements.append({
          "name": gpv.display_name,
          "url": gpv.target_url,
          "type": gpv.placement_type.name,
          "impressions": m.impressions,
          "clicks": m.clicks,
          "conversions": m.conversions,
          "cost": m.cost_micros / 1_000_000,
          "ctr": m.ctr,
      })

  top_converting = [p for p in placements if p["conversions"] > 0]
  bottom_spend = sorted(
      [p for p in placements if p["conversions"] == 0],
      key=lambda p: -p["cost"]
  )[:10]

  placement_block = "\n".join(
      f"- [{p['type']}] {p['name']} ({p['url']}) — conv: {p['conversions']:.0f}, CTR: {p['ctr']:.2%}, cost: ${p['cost']:.2f}"
      for p in top_converting[:25]
  )

  waste_block = "\n".join(
      f"- [{p['type']}] {p['name']} — $0 conversions, spent ${p['cost']:.2f}, {p['impressions']} impressions"
      for p in bottom_spend
  )

  mave = requests.post(
      "https://app.mavera.io/api/v1/mave/chat",
      headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
      json={"message": f"""Analyze these Google Display/YouTube placements and provide strategic recommendations.

  TOP CONVERTING PLACEMENTS ({len(top_converting)}):
  {placement_block}

  TOP WASTED SPEND (0 conversions):
  {waste_block}

  Produce:
  1. Content themes that drive conversions (group placements by topic/category)
  2. Which YouTube channels or site categories perform best and why
  3. Recommended placement exclusions (categories burning budget)
  4. Content partnership opportunities (sites worth direct deals)
  5. Audience insight: what do converting placements tell us about our buyer?"""},
  ).json()

  print("--- Placement Analysis ---")
  print(mave.get("content", "")[:2500])
  ```

  ```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 group_placement_view.display_name, group_placement_view.target_url,
      group_placement_view.placement_type, metrics.impressions, metrics.clicks,
      metrics.conversions, metrics.cost_micros, metrics.ctr
    FROM group_placement_view
    WHERE segments.date DURING LAST_30_DAYS AND metrics.impressions > 100
    ORDER BY metrics.conversions DESC LIMIT 50`;

  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 placements = (gaRes[0]?.results || []).map((row) => ({
    name: row.groupPlacementView.displayName,
    url: row.groupPlacementView.targetUrl,
    type: row.groupPlacementView.placementType,
    impressions: parseInt(row.metrics.impressions),
    clicks: parseInt(row.metrics.clicks),
    conversions: parseFloat(row.metrics.conversions),
    cost: parseInt(row.metrics.costMicros) / 1_000_000,
    ctr: parseFloat(row.metrics.ctr),
  }));

  const topConverting = placements.filter((p) => p.conversions > 0);
  const wastedSpend = placements.filter((p) => p.conversions === 0).sort((a, b) => b.cost - a.cost).slice(0, 10);

  const placementBlock = topConverting.slice(0, 25)
    .map((p) => `- [${p.type}] ${p.name} (${p.url}) — conv: ${p.conversions.toFixed(0)}, CTR: ${(p.ctr * 100).toFixed(1)}%, cost: $${p.cost.toFixed(2)}`)
    .join("\n");
  const wasteBlock = wastedSpend
    .map((p) => `- [${p.type}] ${p.name} — $0 conv, spent $${p.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: `Analyze these Display/YouTube placements.\n\nTOP CONVERTING:\n${placementBlock}\n\nWASTED SPEND:\n${wasteBlock}\n\nProduce: 1) Content themes 2) Best categories 3) Exclusions 4) Partnership opps 5) Buyer insight`,
    }),
  }).then((r) => r.json());

  console.log("--- Placement Analysis ---");
  console.log((mave.content || "").slice(0, 2500));
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
--- Placement Analysis ---

## Converting Content Themes
1. **B2B SaaS Review Sites** (G2, Capterra, TrustRadius) — 42% of conversions.
   Buyers are actively evaluating. Increase bids on these placements.
2. **Marketing YouTube Channels** (HubSpot, Neil Patel, Ahrefs) — 28% of conversions.
   Educational content contexts outperform entertainment.
3. **Industry Blogs** (MarTech.org, Search Engine Journal) — 18% of conversions.

## Recommended Exclusions
- Mobile game apps: $2,340 spent, 0 conversions. Add `adsenseformobileapps.com` to exclusion list.
- Parenting/lifestyle blogs: High impressions, 0 conversions. Exclude category.

## Partnership Opportunities
- G2 profile drives 15% of Display conversions at $4.20 CPA. Consider a sponsored profile.
- MarTech.org: 8 conversions at $6.10 CPA. Direct sponsorship would lower cost.

## Buyer Insight
Your converting buyers consume professional review content and educational marketing
videos. They're in active evaluation mode, not casual browsing.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Placement view requires Display/Video campaigns">Search-only accounts return empty results for `group_placement_view`. Ensure you have active Display or YouTube campaigns.</Accordion>
  <Accordion title="Mave context limits">50 placements with URLs can produce a large prompt. The code limits to 25 converting + 10 wasted. For larger analyses, batch into multiple Mave calls.</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>
