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

# Device Behavior → Creative Format Recommendations

> Pull GA4 device category, screen resolution, and engagement metrics to get Mave Agent recommendations on optimal ad formats, content layouts, and creative dimensions per device segment

### Scenario

Your desktop and mobile users behave differently — session lengths, engagement patterns, scroll depth, and conversion rates all vary by device. You pull device category, screen resolution, and engagement metrics from GA4, then send the behavioral breakdown to Mave for creative format recommendations. The result tells you which ad formats, content layouts, and creative dimensions to prioritize for each device segment.

### Architecture

```mermaid theme={"dark"}
flowchart LR
    A["GA4 RunReport (deviceCategory × screenResolution)"] --> B[Behavioral profile per device] --> C["POST /api/v1/mave/chat"] --> D[Creative format recommendations per device]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests
  from google.analytics.data_v1beta import BetaAnalyticsDataClient
  from google.analytics.data_v1beta.types import (
      RunReportRequest, Dimension, Metric, DateRange, OrderBy,
  )

  PROPERTY_ID = os.environ["GA4_PROPERTY_ID"]
  MV = os.environ["MAVERA_API_KEY"]

  client = BetaAnalyticsDataClient()

  device_report = client.run_report(RunReportRequest(
      property=f"properties/{PROPERTY_ID}",
      dimensions=[
          Dimension(name="deviceCategory"),
          Dimension(name="screenResolution"),
      ],
      metrics=[
          Metric(name="totalUsers"),
          Metric(name="sessions"),
          Metric(name="engagementRate"),
          Metric(name="averageSessionDuration"),
          Metric(name="conversions"),
          Metric(name="screenPageViewsPerSession"),
      ],
      date_ranges=[DateRange(start_date="30daysAgo", end_date="today")],
      order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="totalUsers"), desc=True)],
      limit=100,
  ))

  from collections import defaultdict

  devices = defaultdict(lambda: {
      "users": 0, "sessions": 0, "conversions": 0,
      "eng_sum": 0, "dur_sum": 0, "pages_sum": 0,
      "resolutions": defaultdict(int),
  })

  for row in device_report.rows:
      cat = row.dimension_values[0].value
      res = row.dimension_values[1].value
      users = int(row.metric_values[0].value)
      sessions = int(row.metric_values[1].value)
      engagement = float(row.metric_values[2].value)
      duration = float(row.metric_values[3].value)
      conversions = int(row.metric_values[4].value)
      pages_per = float(row.metric_values[5].value)

      devices[cat]["users"] += users
      devices[cat]["sessions"] += sessions
      devices[cat]["conversions"] += conversions
      devices[cat]["eng_sum"] += engagement * users
      devices[cat]["dur_sum"] += duration * users
      devices[cat]["pages_sum"] += pages_per * sessions
      devices[cat]["resolutions"][res] += users

  device_block = []
  for cat, data in sorted(devices.items(), key=lambda x: -x[1]["users"]):
      avg_eng = data["eng_sum"] / max(data["users"], 1)
      avg_dur = data["dur_sum"] / max(data["users"], 1)
      avg_pages = data["pages_sum"] / max(data["sessions"], 1)
      conv_rate = data["conversions"] / max(data["users"], 1)
      top_res = sorted(data["resolutions"].items(), key=lambda x: -x[1])[:5]
      res_str = ", ".join(f"{r}: {n}" for r, n in top_res)

      device_block.append(
          f"**{cat.upper()}**\n"
          f"  Users: {data['users']} | Sessions: {data['sessions']}\n"
          f"  Engagement: {avg_eng:.0%} | Avg duration: {avg_dur:.0f}s | Pages/session: {avg_pages:.1f}\n"
          f"  Conversions: {data['conversions']} ({conv_rate:.2%})\n"
          f"  Top resolutions: {res_str}"
      )

  device_summary = "\n\n".join(device_block)

  mave = requests.post(
      "https://app.mavera.io/api/v1/mave/chat",
      headers={"Authorization": f"Bearer {MV}", "Content-Type": "application/json"},
      json={"message": f"""Recommend creative format adjustments for each device category based on this GA4 behavioral data.

  DEVICE BEHAVIORAL PROFILES (last 30 days):
  {device_summary}

  For each device category, provide:
  1. Recommended ad creative dimensions and formats (static, video, carousel, etc.)
  2. Optimal content layout (long-form vs. snackable, scroll depth expectations)
  3. CTA placement recommendations based on session duration and pages/session
  4. Landing page design considerations for the top screen resolutions
  5. Content format priorities (video length, image aspect ratio, text density)
  6. Specific do's and don'ts for creative on this device

  Also provide cross-device recommendations:
  - Which messages to keep consistent across devices
  - Which elements to adapt per device
  - Mobile-first vs. desktop-first content strategy recommendation"""},
  ).json()

  print("--- Device-Specific Creative Recommendations ---")
  print(mave.get("content", "")[:3000])
  ```

  ```javascript JavaScript theme={"dark"}
  const MV = process.env.MAVERA_API_KEY;
  const PROPERTY_ID = process.env.GA4_PROPERTY_ID;
  const KEY_FILE = JSON.parse(require("fs").readFileSync(process.env.GOOGLE_APPLICATION_CREDENTIALS, "utf8"));

  const { GoogleAuth } = require("google-auth-library");
  const auth = new GoogleAuth({
    credentials: KEY_FILE,
    scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
  });
  const accessToken = await auth.getAccessToken();

  const gaRes = await fetch(
    `https://analyticsdata.googleapis.com/v1beta/properties/${PROPERTY_ID}:runReport`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        dimensions: [{ name: "deviceCategory" }, { name: "screenResolution" }],
        metrics: [
          { name: "totalUsers" }, { name: "sessions" },
          { name: "engagementRate" }, { name: "averageSessionDuration" },
          { name: "conversions" }, { name: "screenPageViewsPerSession" },
        ],
        dateRanges: [{ startDate: "30daysAgo", endDate: "today" }],
        orderBys: [{ metric: { metricName: "totalUsers" }, desc: true }],
        limit: 100,
      }),
    }
  ).then((r) => r.json());

  const devices = {};
  for (const row of gaRes.rows || []) {
    const cat = row.dimensionValues[0].value;
    const res = row.dimensionValues[1].value;
    const users = parseInt(row.metricValues[0].value);
    const sessions = parseInt(row.metricValues[1].value);
    const eng = parseFloat(row.metricValues[2].value);
    const dur = parseFloat(row.metricValues[3].value);
    const conv = parseInt(row.metricValues[4].value);
    const pps = parseFloat(row.metricValues[5].value);

    devices[cat] ??= { users: 0, sessions: 0, conv: 0, engSum: 0, durSum: 0, ppsSum: 0, resolutions: {} };
    devices[cat].users += users;
    devices[cat].sessions += sessions;
    devices[cat].conv += conv;
    devices[cat].engSum += eng * users;
    devices[cat].durSum += dur * users;
    devices[cat].ppsSum += pps * sessions;
    devices[cat].resolutions[res] = (devices[cat].resolutions[res] || 0) + users;
  }

  const deviceBlocks = Object.entries(devices)
    .sort(([, a], [, b]) => b.users - a.users)
    .map(([cat, d]) => {
      const avgEng = d.engSum / (d.users || 1);
      const avgDur = d.durSum / (d.users || 1);
      const avgPps = d.ppsSum / (d.sessions || 1);
      const topRes = Object.entries(d.resolutions).sort(([, a], [, b]) => b - a).slice(0, 5)
        .map(([r, n]) => `${r}: ${n}`).join(", ");
      return `**${cat.toUpperCase()}**\n  Users: ${d.users} | Sessions: ${d.sessions}\n  Engagement: ${(avgEng * 100).toFixed(0)}% | Duration: ${avgDur.toFixed(0)}s | Pages/session: ${avgPps.toFixed(1)}\n  Conv: ${d.conv} (${(d.conv / (d.users || 1) * 100).toFixed(2)}%)\n  Resolutions: ${topRes}`;
    }).join("\n\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: `Recommend creative format adjustments per device:\n\n${deviceBlocks}\n\nFor each device: 1) Ad dimensions/formats 2) Content layout 3) CTA placement 4) Landing page design 5) Content format priorities 6) Do's/don'ts.\n\nAlso: cross-device consistency, adaptation points, mobile-first vs desktop-first recommendation.`,
    }),
  }).then((r) => r.json());

  console.log("--- Device Creative Recommendations ---");
  console.log((mave.content || "").slice(0, 3000));
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
--- Device-Specific Creative Recommendations ---

## Mobile (62% of users, 1.8% conv rate)
- **Ad formats:** 9:16 vertical video (15s max), story-format carousels, single image 1080×1080
- **Content layout:** Snackable — one idea per screen, bullet points over paragraphs. Your 45s avg session means they decide in the first scroll.
- **CTA placement:** Fixed bottom bar or within first viewport. With 2.1 pages/session, they won't scroll far.
- **Landing pages:** Optimize for 390×844 (iPhone 14/15). Single-column, thumb-zone CTAs, collapse feature tables into accordions.
- **Don't:** Use horizontal video, multi-column layouts, or forms with more than 3 fields.

## Desktop (31% of users, 3.4% conv rate)
- **Ad formats:** 16:9 landscape video (30-60s), comparison infographics, multi-panel carousel
- **Content layout:** Long-form works here — 3.2 min avg session and 4.8 pages/session means they're evaluating deeply. Include detailed feature tables, customer quotes, and embedded demos.
- **CTA placement:** After value demonstration (not above the fold). Desktop users scroll.
- **Landing pages:** Optimize for 1920×1080. Two-column layouts with sticky nav. Include pricing calculator or interactive demo.

## Cross-Device
- **Keep consistent:** Value proposition, brand colors, core messaging hierarchy
- **Adapt:** CTA text (mobile: "Try Free" / desktop: "Start Your 14-Day Free Trial"), content depth, form length
- **Recommendation:** Mobile-first design, desktop-enhanced. 62% of traffic is mobile, but desktop converts at nearly 2x — invest in both, but design mobile first.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Screen resolution cardinality">Hundreds of unique resolutions exist. The code aggregates by device category first, then lists top 5 resolutions per category. Group similar resolutions (e.g. 1920×1080 and 1920×1200 as "Full HD") for cleaner analysis.</Accordion>
  <Accordion title="Tablet traffic declining">If tablet traffic is under 5% of total, consider merging tablet data with desktop for persona purposes. Modern tablets render desktop-class pages.</Accordion>
  <Accordion title="Smart TV / other devices">GA4 may report `smart tv` or other device categories with minimal traffic. Filter these out unless you specifically target living-room experiences.</Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="GA4 Integration" icon="chart-line" href="/integrations/ga4">
    Back to GA4 integration overview
  </Card>

  <Card title="Acquisition Channel × Persona Mapping" icon="users" href="/integrations/ga4/channel-persona-mapping">
    Map channel-demographic pairs to personas
  </Card>

  <Card title="Audience Demographics → Persona Creation" icon="user-plus" href="/integrations/ga4/audience-demographics-personas">
    Create personas from GA4 demographic data
  </Card>

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