Refresh stale content automatically when rankings drop
When a high-value page drops in rank, the agent audits it against the current SERP, regenerates weak sections, and queues an approval-gated refresh — same loop for AI citation drops.
When a high-value page drops in rank, CitationBench audits it, drafts an update, and queues it for approval. Same loop works for AI citation drops.
| Outcome | Detected drops → graded → refreshed → approved → republished → re-indexed |
| Time | ~3 min per refresh draft; runs continuously |
| Cost | ~30 credits per refresh + downstream publish (1) + indexing (1) |
| Prereqs | Workspace with keywords + published content, GSC connected (for indexing), DataForSEO connected (for rank tracking) |
What it does
rank_monitor (scheduled) detects drop of 5+ on a FOCUSED keyword
↓ on detection, spawns refresh_stale invocation
refresh_stale:
produce.evaluate (current article vs current SERP)
↓ identifies weak sections + missing topics
produce.blog_post.regenerate (scope: weak sections)
↓
produce.refine (workspace standard refiner chain)
↓ pauses at WAITING_APPROVAL with the draft
↓ on approval:
produce.publish (auto-fires indexing.gsc.submit + indexnow)Step 1 — Identify content worth monitoring
Not every page deserves auto-refresh. Filter to:
- Content linked to
PURCHASEorALTERNATIVE-intent keywords - Content that's been published > 90 days
- Content with
priority: HIGHorCRITICAL
curl -X POST .../v1/produce/blog-post -G \
--data-urlencode "status=PUBLISHED" \
--data-urlencode "publishedBefore=$(date -u -d '90 days ago' -Iseconds)" \
--data-urlencode "tag=monitor-rank"Tag these as monitor-rank so the rank_monitor skill picks them up.
Step 2 — Wire rank monitoring → refresh_stale
curl -X POST .../v1/agent/invoke -d '{
"skill": "rank_monitor",
"input": {
"scope": { "tag": "monitor-rank" },
"alertOn": { "drop": 5 },
"onDrop": {
"invokeSkill": "refresh_stale",
"approval": { "required": true }
},
"schedule": "weekly:mon:09:00"
}
}'Now every Monday, ranks get checked; any drop of 5+ spawns a refresh draft.
Step 3 — Watch + approve drafts
The refresh_stale invocation pauses at WAITING_APPROVAL with the draft + the eval report (showing exactly why it changed each section).
curl -G .../v1/agent/approvals \
--data-urlencode "skill=refresh_stale" \
--data-urlencode "scope=workspace"Each approval surfaces a preview like:
{
"approvalId": "appr_***",
"invocationId": "inv_***",
"skill": "refresh_stale",
"step": "publish_refreshed",
"preview": {
"blogPostId": "bp_***",
"currentPublishedUrl": "https://acme.com/blog/engineering-team-capacity-tracking",
"previousScore": 62,
"newScore": 81,
"sectionsRewritten": ["intro", "section_3_forecasting"],
"diff": {
"wordsAdded": 840,
"wordsRemoved": 312
},
"evaluationReport": { "...": "..." }
}
}Approve in the dashboard, in Slack, or via API:
curl -X POST .../v1/agent/invocations/inv_***/approveStep 4 — Also wire AI citation drops
The same pattern works for GEO. Wire citation_hunter to refresh_stale:
curl -X POST .../v1/agent/invoke -d '{
"skill": "citation_hunter",
"input": {
"queryIds": ["aiq_***"],
"reclamation": "recommend",
"schedule": "daily:08:00"
}
}'Drops in AI citation rates spawn refresh_stale drafts on the content tied to those queries.
One-shot script
#!/usr/bin/env bash
set -euo pipefail
KEY="${CITATIONBENCH_API_KEY:?}"
WS="${WORKSPACE_ID:?}"
BASE="https://api.citationbench.com/v1"
# 1. Tag the content worth monitoring
curl -sf -G $BASE/produce/blog-post \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
--data-urlencode "status=PUBLISHED" \
--data-urlencode "publishedBefore=$(date -u -d '90 days ago' -Iseconds)" \
| jq -r '.data[].id' \
| while read BP; do
curl -sf -X PATCH $BASE/produce/blog-post/$BP \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d '{ "tags": ["monitor-rank"] }'
done
# 2. Wire rank monitor → refresh_stale
curl -sf -X POST $BASE/agent/invoke \
-H "Authorization: Bearer $KEY" -H "X-Workspace-Id: $WS" \
-d '{
"skill": "rank_monitor",
"input": {
"scope": { "tag": "monitor-rank" },
"alertOn": { "drop": 5 },
"onDrop": { "invokeSkill": "refresh_stale", "approval": { "required": true } },
"schedule":"weekly:mon:09:00"
}
}'
echo "Stale-content refresh active."Gotchas
- Approval is essential for refresh. Auto-publishing a rewritten article without human eyes is a recipe for breaking your voice. Keep
approval.required: true. - Some drops are SERP volatility, not your fault. If a competitor temporarily ranks higher, you might trigger a refresh you didn't need. Set a higher threshold (drop ≥ 8) or require sustained drop over 2 weeks.
- The agent uses the current SERP as a benchmark. If the SERP is dominated by content types you don't produce (e.g., a video carousel), the refresh might overshoot. Manually review.
- Indexing lag. Even after re-publishing, Google may take 1–4 weeks to re-evaluate. Don't expect instant rank recovery.
Related
- API: Agent · invoke
- API: Production · evaluate
- API: Production · refine
- API: Indexing · gsc index
- Playbook: Weekly rank checks across all clients
- Playbook: Track ChatGPT citations daily
Build an SEO agent
A reusable Claude Code project that runs end-to-end SEO ops via the CitationBench MCP server — tell Claude what you want, it composes tools, persists results, and pauses for approval.
Daily Reddit monitoring
Every morning, scrape your ICP's subreddits, score pain points by signal quality, and surface the top content opportunities — plus auto-generated blog briefs ready for approval.