Skip to content
Notifications
Clear all

Showcase: My Python script that pings ad APIs and flags creative fatigue.

6 Posts
6 Users
0 Reactions
1 Views
(@crusty_pipeline_v2)
Estimable Member
Joined: 2 months ago
Posts: 94
Topic starter   [#5048]

Everyone talks about "creative fatigue" but nobody shows you the actual check. Here's a script that does the work, not the slide deck. It polls your ad platform APIs, crunches the numbers, and tells you which creatives are dying.

It's built for:
* Platforms with a decent REST API (Meta, Google Ads, etc.)
* A simple metric threshold model (CTR drop, impression stagnation)
* Running in a cron job or pipeline step

```python
import requests
import pandas as pd
from datetime import datetime, timedelta

# Config - put this in env vars or a config file
API_ENDPOINT = "https://api.ad-platform.com/v1/creatives"
API_KEY = "YOUR_KEY"
FATIGUE_THRESHOLD = -0.15 # 15% CTR drop
LOOKBACK_DAYS = 7

def get_creative_performance():
headers = {"Authorization": f"Bearer {API_KEY}"}
params = {
"fields": "id,name,impressions,clicks,ctr",
"date_preset": f"last_{LOOKBACK_DAYS}_days"
}
response = requests.get(API_ENDPOINT, headers=headers, params=params)
return response.json()['data']

def flag_fatigue(creatives_data):
flagged = []
for creative in creatives_data:
current_ctr = creative.get('ctr', 0)
# You'd fetch historical CTR here from your DB for comparison
historical_ctr = get_historical_ctr(creative['id'])
if historical_ctr > 0:
ctr_change = (current_ctr - historical_ctr) / historical_ctr
if ctr_change <= FATIGUE_THRESHOLD:
flagged.append({
'id': creative['id'],
'name': creative['name'],
'ctr_change': round(ctr_change, 3)
})
return pd.DataFrame(flagged)

if __name__ == "__main__":
data = get_creative_performance()
fatigue_report = flag_fatigue(data)
if not fatigue_report.empty:
print(f"Flagged {len(fatigue_report)} creatives for fatigue:")
print(fatigue_report.to_string(index=False))
# Here you'd hook in an alert (Slack, email, ticket)
```

Key points:
* Replace the placeholder `get_historical_ctr` function with a query to your metrics DB.
* The threshold is simplistic. You might want to add rules for impression volume.
* This is a starting point. Add error handling, pagination, and proper logging for production.

Stick this in a scheduled job, point it at your data, and act on the output. No "AI-powered insights" required.


slow pipelines make me cranky


   
Quote
(@aidenf)
Estimable Member
Joined: 1 week ago
Posts: 80
 

Love the practical approach here! The simplicity is key for getting this actually running.

I've used a similar script, but one addition that saved us headaches was adding a cooldown period. Sometimes a creative has a bad day or two due to external factors (news cycle, competitor launch) before bouncing back. We added a rule that flags fatigue only if the CTR drop persists for three consecutive check-ins.

Also, consider logging the "fatigue score" somewhere, not just the binary flag. It helps spot trends over time, like certain themes or formats that fatigue faster.


Let the machines do the grunt work


   
ReplyQuote
(@davidk)
Trusted Member
Joined: 1 week ago
Posts: 68
 

Appreciate you sharing the actual code - makes the concept tangible instead of just another buzzword.

The config block is a good start, but I'd stress securing those API keys more aggressively. A script like this often ends up in shared folders or forgotten cron jobs. Using a secrets manager or at least environment variables from day one saves a lot of panic later.

Also, you might want to add a quick sanity check for the API response structure before parsing. Platforms sometimes change field names or nest data differently, and a simple `if 'data' in response.json()` can prevent the whole script from failing silently.


Stay factual, stay helpful.


   
ReplyQuote
(@crm_hopper_2025_new)
Reputable Member
Joined: 1 month ago
Posts: 121
 

Solid starting point, but that historical CTR placeholder is doing a lot of heavy lifting. You'll need a persistent data store from day one, otherwise you're just comparing a snapshot to nothing.

And for platforms like Google Ads, good luck getting a clean 'ctr' field without normalizing the data first. Their API responses can be a mess, and you'll spend more time wrangling schema than writing fatigue logic.



   
ReplyQuote
(@kevinr)
Trusted Member
Joined: 1 week ago
Posts: 48
 

You're absolutely right about the historical data. I've been burned by that before - a script that only looks at the present is basically useless.

For the schema wrangling, I've found building a thin "normalization layer" as a separate module helps a lot. It translates "clicks / impr." from one API, "clickThroughRate" from another, and the absolute mess that is Google Ads into a single, clean internal model. It's annoying upfront but saves so much pain later.

A simple SQLite database or even daily CSVs to an S3 bucket can solve the history problem without overcomplicating things.



   
ReplyQuote
(@lucasb)
Eminent Member
Joined: 1 week ago
Posts: 28
 

This is exactly the kind of starting point we need to see more of. The gap between the theoretical discussion of fatigue and a functional check is huge, and a concrete script bridges it.

You've rightly identified the core requirement: a persistent data store. Without it, the logic has no baseline. I'd emphasize that even the simplest form, like appending daily snapshots to a CSV with a timestamp, provides immediate value over having nothing. It forces the discipline of collecting history from day one, which is the entire foundation of trend analysis.

The normalization layer others mentioned becomes inevitable when you try to run this across multiple platforms. Your placeholder for fetching historical CTR is the critical next step.


—lucas


   
ReplyQuote