MGID Creative Fatigue Detector
Detects teaser fatigue via period-over-period stats. Rules live here.
Tools: tool reference
Assumptions: threshold assumptions
Required inputs
| Input | Required | Default |
|---|---|---|
| Campaign ID(s) | Yes | simulator scope list, user ids, or named campaigns when run standalone |
| Current period | Yes | last 7 days |
| Baseline period | Yes | prior 7 days (days 8–14) |
| Target CPA | No | from campaign stats or user |
Campaign type gate
Only content, product and push campaigns have teasers. Skip search_feed
and newsletter and say so in the output — see the campaign-type table in
tool reference. On push, widget-level
signals are absent, so rely on teaser-level CTR/CPA only.
Refresh recommendations name what to replace, never the replacement copy. New headlines and images come from MGID's own creative generation, not from the agent.
MCP tools
| Tool | Purpose |
|---|---|
run_custom_report |
Primary — all teasers + metrics in one call per campaign (requires customReports grant) |
list_teasers |
Active teaser count + status guardrail (no metrics) |
get_teaser |
Config, moderation status |
get_teaser_statistics |
Fallback — per teaser when custom report unavailable |
get_campaign_statistics |
Campaign avg CTR/CPA for signal #2 and #5 |
Bulk stats via custom report
One run_custom_report per campaign beats N× get_teaser_statistics. Use
dimension teaserId, filter by filters.campaigns, and the same date
windows as other signal skills.
ads-mcp implementation: run_custom_report calls the v2 statistics reports
endpoint, which accepts filters.dateRange only — no dateRangePrevious.
There is no comparison mode through ads-mcp today.
Period-over-period: run two reports per campaign — current window and
baseline window — and join rows on teaserId. Still 2 calls/campaign, not
one per teaser.
Example params (current 7d, top spenders first):
{
"dimensions": ["teaserId"],
"metrics": ["clicks", "spent", "ctr", "conversionsBuy", "conversionsCostBuy"],
"filters": {
"campaigns": [12061865],
"dateRange": {
"dateFrom": "2026-06-30T00:00:00.000+03:00",
"dateTo": "2026-07-06T23:59:59.999+03:00"
}
},
"orders": { "spent": "desc" },
"limit": 100
}
Repeat with dateRange set to the prior 7d for baseline metrics.
Paginate with offset when meta.total > limit. Skip teasers with zero
current spend unless list_teasers shows them as active and recently launched.
Grant missing: fall back to get_teaser_statistics per teaser; cap at 20
teasers (prior-spend order from a single-period custom report is unavailable —
use list_teasers + campaign stats to pick candidates).
Minimum data (ASSUMPTION)
Per teaser, in current period:
- clicks ≥ 100, OR
- impressions ≥ 1,000 (if returned by statistics tool)
Skip non-active teasers. Count active teasers — never pause if only one remains.
Fatigue signals (ASSUMPTION)
Compare current 7d vs prior 7d only (no day-over-day — too noisy on native):
- CTR drop ≥ 30% with min clicks
- CPA increase ≥ 50% vs campaign avg with ≥ 5 conversions
- CVR drop ≥ 35% vs baseline with ≥ 200 clicks
- Spend ≥ $30, conversionsBuy = 0, clicks ≥ 150
- Teaser CPA > 2× best active teaser (both ≥ 5 conversions)
Recommendations
pause_teaserwhen fatigue + ≥ 2 other active teaserslaunch_teaser_variant— guidance only; no create/moderation MCP tools yet- Rejected teasers: explain via
get_teaser; user fixes in UI — nosend_to_moderationtool
Impact (ASSUMPTION)
spend_removed = teaser_spend_7d / 7
recovery_rate = 0.3 to 0.7 (sibling absorption)
net_spend_delta = -spend_removed × (1 - recovery_rate)
Apply
set_teaser_status (write scope).
Output
Chat: table of teasers to pause/monitor.
Return: Recommendation[].