How to Create an AI-Powered Content Refresh System
Identify and update outdated content automatically to maintain rankings.
Jay Banlasan
The AI Systems Guy
Traffic from old content decays. Google rewards freshness, especially for topics where the landscape changes. This ai content refresh update outdated articles system identifies which of your articles are losing rankings, pulls fresh information on the topic, and generates a specific update plan for each one. You decide what to act on. The system just removes the guesswork about where to start.
Refreshing existing content is almost always higher ROI than creating new content. You already have the domain authority and the backlinks. You just need the article to reflect what is true today.
What You Need Before Starting
- Python 3.10 or higher
- Anthropic API key
- SerpAPI key
- Google Analytics 4 API access
pip install anthropic requests google-analytics-data python-dotenv
Step 1: Identify Decaying Content
Decaying content shows a consistent traffic drop month over month. Pull this from GA4:
import os
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Dimension, Metric
from google.oauth2 import service_account
from dotenv import load_dotenv
load_dotenv()
def get_ga4_client():
credentials = service_account.Credentials.from_service_account_file(
os.getenv("GOOGLE_CREDENTIALS_PATH"),
scopes=["https://www.googleapis.com/auth/analytics.readonly"]
)
return BetaAnalyticsDataClient(credentials=credentials)
def get_page_traffic_comparison(ga_client, property_id: str) -> list:
"""Compare last 30 days vs prior 30 days for content pages."""
def fetch_period(start, end):
request = RunReportRequest(
property=f"properties/{property_id}",
date_ranges=[DateRange(start_date=start, end_date=end)],
dimensions=[Dimension(name="pagePath"), Dimension(name="pageTitle")],
metrics=[Metric(name="screenPageViews"), Metric(name="newUsers")],
limit=100
)
response = ga_client.run_report(request)
pages = {}
for row in response.rows:
path = row.dimension_values[0].value
if not any(p in path for p in ["/blog/", "/articles/", "/systems/", "/guides/"]):
continue
pages[path] = {
"title": row.dimension_values[1].value,
"views": int(row.metric_values[0].value),
"new_users": int(row.metric_values[1].value)
}
return pages
recent = fetch_period("30daysAgo", "today")
prior = fetch_period("60daysAgo", "31daysAgo")
results = []
for path, data in recent.items():
prior_views = prior.get(path, {}).get("views", 0)
current_views = data["views"]
if prior_views == 0:
continue
change_pct = ((current_views - prior_views) / prior_views) * 100
results.append({
"path": path,
"title": data["title"],
"current_views": current_views,
"prior_views": prior_views,
"change_pct": round(change_pct, 1),
"status": "decaying" if change_pct < -15 else "stable" if abs(change_pct) <= 15 else "growing"
})
return sorted(results, key=lambda x: x["change_pct"])
Step 2: Check Current SERP Position
You want to know if the traffic drop is paired with a ranking drop:
import requests
def check_serp_position(keyword: str) -> dict:
url = "https://serpapi.com/search"
params = {
"q": keyword,
"api_key": os.getenv("SERPAPI_KEY"),
"num": 20,
"hl": "en",
"gl": "us"
}
response = requests.get(url, params=params)
data = response.json()
your_domain = os.getenv("YOUR_DOMAIN", "yoursite.com")
for i, result in enumerate(data.get("organic_results", []), 1):
if your_domain in result.get("link", ""):
return {
"keyword": keyword,
"position": i,
"url": result["link"],
"title": result.get("title", "")
}
return {"keyword": keyword, "position": None, "url": "", "title": ""}
Step 3: Generate the Refresh Plan
import anthropic
import json
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
def generate_refresh_plan(
article_title: str,
article_url: str,
article_content: str,
current_serp_position: int,
traffic_change_pct: float
) -> dict:
prompt = f"""You are an SEO content strategist. Generate a specific refresh plan for this article.
ARTICLE: {article_title}
URL: {article_url}
CURRENT RANKING POSITION: {current_serp_position or "Not ranking in top 20"}
TRAFFIC CHANGE (30-day): {traffic_change_pct:+.1f}%
CURRENT ARTICLE CONTENT (first 2000 words):
---
{article_content[:2000]}
---
Analyze why this article may be losing traffic and create a specific refresh plan.
Return as JSON:
{{
"diagnosis": "One paragraph on what is likely causing the traffic drop",
"urgency": "high/medium/low",
"refresh_type": "minor_update/major_rewrite/structural_overhaul",
"estimated_effort_hours": 0,
"specific_updates": [
{{
"type": "add/update/remove/restructure",
"section": "which section or element",
"instruction": "specific instruction for the writer",
"priority": "must/should/nice-to-have"
}}
],
"new_sections_to_add": [],
"outdated_sections_to_remove": [],
"keyword_opportunities": [],
"predicted_outcome": "What you expect to happen after the refresh"
}}"""
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
raw = message.content[0].text.strip()
if raw.startswith("```"):
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:]
return json.loads(raw)
Step 4: Generate the Refreshed Content
Once you have a plan, execute it:
def refresh_article(original_content: str, refresh_plan: dict) -> str:
updates_str = json.dumps(refresh_plan["specific_updates"], indent=2)
new_sections_str = "\n".join(f"- {s}" for s in refresh_plan.get("new_sections_to_add", []))
remove_str = "\n".join(f"- {s}" for s in refresh_plan.get("outdated_sections_to_remove", []))
prompt = f"""Update this article according to the refresh plan below.
REFRESH TYPE: {refresh_plan['refresh_type']}
DIAGNOSIS: {refresh_plan['diagnosis']}
SPECIFIC UPDATES TO MAKE:
{updates_str}
NEW SECTIONS TO ADD:
{new_sections_str or "None"}
SECTIONS TO REMOVE OR TRIM:
{remove_str or "None"}
ORIGINAL ARTICLE:
---
{original_content}
---
Produce the complete refreshed article. Keep what is working. Fix what is not.
Maintain the original author's voice. Do not add fluff to increase word count.
If you add new information, make sure it is accurate and specific."""
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=4000,
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].text
if __name__ == "__main__":
ga_client = get_ga4_client()
property_id = os.getenv("GA4_PROPERTY_ID")
traffic_data = get_page_traffic_comparison(ga_client, property_id)
decaying = [p for p in traffic_data if p["status"] == "decaying"][:10]
print(f"Found {len(decaying)} decaying articles\n")
for page in decaying[:3]:
print(f"Planning refresh for: {page['title']} ({page['change_pct']:+.1f}%)")
with open(f"content/{page['path'].split('/')[-1]}.md", "r") as f:
content = f.read()
plan = generate_refresh_plan(
article_title=page["title"],
article_url=page["path"],
article_content=content,
current_serp_position=None,
traffic_change_pct=page["change_pct"]
)
with open(f"refresh-plans/{page['path'].split('/')[-1]}-plan.json", "w") as f:
json.dump(plan, f, indent=2)
print(f" Urgency: {plan['urgency']}, Type: {plan['refresh_type']}")
What to Build Next
- Set up a monthly cron job that automatically identifies your top 5 decaying articles and emails you a refresh priority list
- Build a publish date updater that changes the article date to the refresh date after the update is published, which signals freshness to Google
- Create a refresh tracking system that monitors ranking changes in the weeks after each refresh to build a data set on what works
Related Reading
- How to Build an AI Blog Post Generator - Generate new sections to add to refreshed articles
- How to Create Automated Content Performance Reports - Track traffic recovery after refreshes
- How to Build an AI Script Writer for Video Content - Add video embeds to refreshed articles to boost time on page
Want this system built for your business?
Get a free assessment. We will map every system your business needs and show you the ROI.
Get Your Free Assessment