Building a Paid UA Reporting Agent: Architecture and Prompt Patterns

Paid UA reporting is one of the most repetitive, high-context tasks a growth team does every week. You pull numbers from Apple Search Ads, Meta, TikTok, and Google App Campaigns. You drop them into a spreadsheet. You write a summary for the team. You flag the anomalies you caught — and miss the ones you didn't. Then you do it again next Monday.
An AI agent can own that entire loop. Not a dashboard. Not a script. An agent — something that reasons over the data, decides what's worth surfacing, and produces a narrative summary a human can act on.
This is a build guide. By the end you'll have a clear architecture, the prompt patterns that actually work, and enough specifics to start implementation without guessing.
What This Agent Needs to Do
Before writing a single prompt, define the agent's job in plain terms. Ambiguity here is where most agent projects go wrong.
A paid UA reporting agent has four core responsibilities:
- Pull data from every active ad channel on a defined schedule
- Normalize that data into a consistent schema (because every platform reports differently)
- Detect anomalies — spend spikes, CPI jumps, install drops, ROAS degradation
- Generate a summary that explains what happened, what changed, and what warrants attention
That's it. The agent shouldn't make bid changes. It shouldn't pause campaigns. Keep the scope narrow. An agent that does one job reliably is worth ten that do six jobs inconsistently.
Architecture Overview
The agent runs on a simple three-layer architecture: data ingestion → analysis → output generation.
[Scheduler]
|
[Data Fetcher] → Apple Search Ads API
→ Meta Marketing API
→ TikTok Ads API
→ Google Ads API (App Campaigns)
|
[Normalizer] → unified daily/weekly metrics schema
|
[Anomaly Detector] → rule-based + LLM-assisted flagging
|
[Report Generator] → LLM prompt → structured markdown/HTML report
|
[Delivery Layer] → Slack / email / Notion / Google Doc
Each layer is a separate function or microservice. Don't try to collapse them into one LLM call — you'll lose debuggability and it'll hallucinate data.
| Layer | Recommended Tool | Notes |
|---|---|---|
| Scheduler | GitHub Actions or cron on EC2 | Weekly trigger, typically Friday EOD or Monday AM |
| Data Fetcher | Python + platform SDKs | One fetcher module per platform |
| Normalizer | Python data model (Pydantic) | Enforces schema before analysis |
| Anomaly Detector | Rule engine + GPT-4o / Claude | Rules catch the obvious; LLM catches the contextual |
| Report Generator | GPT-4o or Claude 3.5 Sonnet | Prompt-driven narrative generation |
| Delivery | Slack Webhooks, SendGrid, Notion API | Pick one to start |
Data Fetching and Normalization
Every ad platform has its own naming conventions, date handling, and metric definitions. Impressions mean the same thing everywhere. "Installs" do not.
Apple Search Ads counts a tap-through install within 30 days. Meta counts app installs as a conversion event configured by the advertiser. TikTok and Google have their own attribution windows. Before your agent can reason about performance, it needs to work from a normalized schema.
Define a DailyMetrics object with fields every platform can populate:
class DailyMetrics(BaseModel):
date: date
platform: str # "apple_search_ads" | "meta" | "tiktok" | "google"
campaign_id: str
campaign_name: str
spend: float # USD
impressions: int
clicks: int
installs: int # platform-reported, not MMP-attributed
cpi: float # spend / installs
ctr: float # clicks / impressions
cvr: float # installs / clicks
currency: str # always normalize to USD before analysis
Pull 14 days of data on each run, not 7. You need the prior week as context for anomaly detection — a CPI that doubled is only notable if you know what it was before.
If your stack uses an MMP (AppsFlyer, Adjust, Branch), pull a parallel dataset from the MMP API and store both. Platform-reported installs and MMP-attributed installs will diverge; your agent should flag that divergence, not paper over it.
Anomaly Detection: Rules First, LLM Second
The temptation is to hand all the data to an LLM and ask it to find problems. That works poorly. LLMs are bad at arithmetic and they'll confidently report wrong percentage changes.
Do the math in code. Flag anomalies with rules. Then pass the flagged anomalies to the LLM for interpretation.
Rule examples:
def detect_anomalies(current: DailyMetrics, prior: DailyMetrics) -> list[Anomaly]:
anomalies = []
# Spend spike
if current.spend > prior.spend * 1.25:
anomalies.append(Anomaly(type="spend_spike", delta_pct=..., severity="high"))
# CPI jump
if current.cpi > prior.cpi * 1.20:
anomalies.append(Anomaly(type="cpi_increase", delta_pct=..., severity="medium"))
# Install drop
if current.installs < prior.installs * 0.75:
anomalies.append(Anomaly(type="install_drop", delta_pct=..., severity="high"))
# CVR collapse (often signals a creative or landing page issue)
if current.cvr < prior.cvr * 0.80:
anomalies.append(Anomaly(type="cvr_drop", delta_pct=..., severity="medium"))
return anomalies
Thresholds should be configurable, not hardcoded. In our engagements, a 20–25% week-over-week CPI increase is typically the right trigger point — below that and you're generating noise.
Once anomalies are calculated and validated in code, pass them to the LLM with the surrounding context.
Prompt Patterns for Report Generation
This is where most implementations stall. The prompt design determines whether your agent produces useful analysis or generic filler.
Pattern 1: Structured context injection
Don't summarize the data before passing it to the LLM. Pass the full structured object. Let the LLM work from raw numbers rather than your paraphrase.
You are a paid UA analyst generating a weekly performance summary.
Here is last week's performance data by platform (JSON):
{{weekly_metrics_json}}
Here are the anomalies detected by the rule engine this week:
{{anomalies_json}}
Here is the prior week's data for comparison:
{{prior_week_metrics_json}}
Generate a weekly UA summary with these sections:
1. Headline (one sentence, what happened this week)
2. Platform breakdown (3-5 bullet points, one per active platform)
3. Anomalies (explain each flagged anomaly in plain language, include the percentage change)
4. Watch list (what to monitor next week and why)
Rules:
- Use the exact numbers from the data. Do not estimate or round unless instructed.
- Do not add recommendations beyond what the data supports.
- Keep the total length under 400 words.
- If an anomaly has an obvious cause visible in the data (e.g., spend increase drove install increase), say so.
Pattern 2: Chain-of-thought for anomaly explanation
For complex anomalies — a CPI spike on one platform but not others, or a CVR drop that doesn't match spend changes — use a chain-of-thought prompt before the final report generation:
Given the following anomaly and supporting data, reason step by step about the most likely cause.
Consider: creative rotation, budget changes, seasonality, platform-level changes, and attribution window effects.
After reasoning, state your conclusion in one sentence.
Anomaly: {{anomaly_object}}
Supporting data: {{relevant_campaign_data}}
Feed the output of this reasoning step into the final report prompt as additional context. The report quality improves noticeably, and more importantly, the reasoning is logged — which matters when something goes wrong.
See our post on agent failure modes for a detailed breakdown of where this kind of chained reasoning can go off the rails in production.
Working with a paid UA team and want this automated? Our mobile app marketing team runs paid acquisition across Apple Search Ads, Meta, TikTok, and Google — and we can layer agent-driven reporting on top of those campaigns.
Handling Multi-Campaign and Multi-App Complexity
If the agent covers one app and four platforms, the architecture above is sufficient. If it covers multiple apps — common for agencies or portfolio companies — you need an additional aggregation layer.
Each app gets its own normalized dataset. The agent runs per-app first, generates per-app summaries, then optionally runs a second pass to generate a portfolio-level roll-up.
Don't try to analyze all apps in a single LLM call. Context windows are large enough, but the prompt complexity creates noise. Separate calls, then aggregate the outputs programmatically or with a lightweight "executive summary" prompt.
For cost modeling on multi-app deployments, the AI agent cost breakdown we published is a useful reference — token costs at scale add up faster than most teams expect.
Delivery and Reliability
A reporting agent that occasionally fails to deliver is worse than no agent. The team starts ignoring it, then they stop trusting it, then someone builds a manual backup and you have two systems to maintain.
Make failures loud. If the data fetch fails for any platform, the report should say so explicitly — not silently omit that platform's data.
Version your prompts. Store prompt templates in version control alongside the code. When the report quality changes unexpectedly, you need to know whether it was a data change or a prompt change.
Log everything. Every agent run should write: data fetched (row counts per platform), anomalies detected (count and types), LLM prompt sent (hash or full text), LLM response received, report delivered (timestamp and destination).
Weekly scheduled runs via GitHub Actions work well at this scale. The cron expression for Friday at 5pm EST is 0 22 * * 5.
FAQ
What LLM should I use for the report generation step?
GPT-4o and Claude 3.5 Sonnet both work well for this task. The choice usually comes down to your existing API relationships and latency requirements. For structured JSON output, both support function calling / tool use — use that instead of asking the model to format JSON in its response.
Can the agent make bid changes or pause underperforming campaigns?
It can be extended to do that, but don't start there. Reporting agents that also act on their findings are harder to trust and much harder to audit when something goes wrong. Get the reporting right first, then add action capabilities incrementally with human approval gates.
How do I handle attribution discrepancies between platform and MMP data?
Store both. Report both. Let the agent flag when they diverge by more than a configured threshold (typically 10–15%). Don't try to reconcile them programmatically — the causes are too varied. Flag the discrepancy and let a human investigate.
How often should the agent run?
Weekly is the right default for most teams. Daily runs generate noise — day-over-day variance in paid UA is normal and rarely actionable. If you need daily monitoring, use the rule engine alone and only escalate to full LLM-generated reports when anomalies are detected.
What happens if an API is down during a scheduled run?
Build retry logic with exponential backoff into each data fetcher. If a platform API fails after retries, log the failure, mark that platform's data as missing, and generate the report anyway — with a clear note that the platform is excluded. Don't skip the whole report because one source failed.
Do I need an MMP to build this agent?
No, but an MMP significantly improves the install data quality. Platform-reported installs are sufficient for anomaly detection and trend analysis. For conversion quality metrics — retention, in-app events, ROAS based on downstream revenue — you need MMP data.
If you're running paid UA across multiple channels and spending time every week on manual reporting, this is a solvable problem. Our mobile app marketing team handles paid acquisition and can scope and build the reporting automation alongside it. If you'd rather talk through the architecture first, book 30 minutes with Marco — no sales pitch, just specifics.