Luka Mrkić
Head of BD
Insights, strategies, and real-world playbooks on AI-powered marketing.
MAY 29, 2026
If you are evaluating who should build this for your team, this guide gives you both the technical blueprint and the standards to evaluate the work.
Run SerpAPI on a cron schedule against a curated keyword list, write every raw JSON response to a store you control, diff each new snapshot against the prior day, then pass the diff plus the relevant SERP blocks to Claude with a strict JSON schema. The model returns position deltas, new SERP features, suspected intent shifts, and competitor movement notes. A weekly job rolls that JSON into a brief that lands in Slack and Notion, reviewed by a human before it goes to the wider team.
![]()
Six stages, each with a clear contract. Keywords are the input. SerpAPI is the data layer. Storage is the memory. The diff job is the change detector. Claude is the analyst. The brief is the artifact your team actually reads.
Start with a small list. Twenty to fifty keywords is enough to validate the pipeline. Each row should carry the term, the intent class, the priority, the geo (gl), the language (hl), and the device. Keep brand and non-brand separate so the brief can split them. Tag each row with the cluster it belongs to so the analysis stays focused.
Use the Google engine with q, location, gl, hl, device, and num set to 100 so you capture the deeper tail. SerpAPI returns organic results, paid results, the answer box, related questions, AI Overviews when present, image and video packs, and the local pack. Persist the full JSON. Keep the raw shape so you can replay it when a new SERP feature appears.
For a small list, Google Sheets or a single Notion database is fine. For production work, use Postgres or SQLite with a snapshots table keyed by (keyword, date, device). Append only. Never overwrite. The history is what makes the analysis credible.
Load today and yesterday for each keyword. Compare organic positions by URL. Flag new entrants, dropped URLs, and position changes. Compare SERP feature presence: AI Overview, featured snippet, PAA count, local pack, image and video packs. Persist the diff alongside the snapshot.
Send the diff plus the top ten organic results and the AI Overview citations to Claude through the Anthropic Messages API with a strict JSON output schema and a system role of senior SEO analyst. Constrain the model to read the deltas and return facts in the schema. The schema gives you keyword, position delta summary, new features, suspected intent shift, and competitor movement notes.
A weekly cron job reads the last seven days of Claude outputs and writes a single brief: top movers, new SERP features, AI Overview entries and exits, and competitor activity. Slack gets the headline. Notion gets the full brief with links back to the raw snapshots.
This is the minimum runnable shape. It fetches a SERP, diffs against a prior snapshot, then asks Claude to analyze the diff with a structured JSON output. Drop it behind a scheduler and add storage.
import os, json, requests, anthropic
SERPAPI_KEY = os.environ["SERPAPI_KEY"]
client = anthropic.Anthropic()
def fetch_serp(keyword, gl="us", hl="en", device="desktop"):
r = requests.get("https://serpapi.com/search.json", params={
"engine": "google",
"q": keyword,
"location": "Austin, Texas, United States",
"gl": gl, "hl": hl, "device": device,
"num": 100, "api_key": SERPAPI_KEY,
}, timeout=60)
r.raise_for_status()
return r.json()
def diff_snapshots(today, yesterday):
deltas = []
pos_y = {x["link"]: x["position"] for x in yesterday.get("organic_results", [])}
pos_t = {x["link"]: x["position"] for x in today.get("organic_results", [])}
for url, p in pos_t.items():
prev = pos_y.get(url)
if prev is None:
deltas.append({"url": url, "change": "new", "position": p})
elif prev != p:
deltas.append({"url": url, "change": "moved", "from": prev, "to": p})
for url in pos_y.keys() - pos_t.keys():
deltas.append({"url": url, "change": "dropped", "from": pos_y[url]})
features = {
"ai_overview": bool(today.get("ai_overview")),
"featured_snippet": bool(today.get("answer_box")),
"people_also_ask": len(today.get("related_questions", [])),
}
return {"deltas": deltas, "features": features}
def analyze_with_claude(keyword, diff):
msg = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
system="You are a senior SEO analyst. Output strict JSON only.",
messages=[{"role": "user", "content": f"""
Keyword: {keyword}
Diff vs yesterday: {json.dumps(diff)}
Return JSON with this schema:
{{
"keyword": str,
"position_delta_summary": str,
"new_features": [str],
"suspected_intent_shift": str,
"competitor_movement_notes": [str]
}}
"""}],
)
return json.loads(msg.content[0].text)
If you want this set up cleanly inside your stack with logging, retries, and a feedback loop into a CRM, that is the kind of work we ship at Espressio.
![]()
Rankings are the obvious signal. The richer signals are the SERP features that surround them. An AI Overview appearing on a tracked term changes the click economics overnight. A featured snippet switching owners often precedes a position one move. Watch the box around the number.
![]()
A weekly screenshot dump tells you nothing about why the SERP moved. A daily fetch with a diff job, a Claude analysis pass, and a written brief turns rank changes into a story your content and PR teams can act on.
![]()
Use the scorecard when buying or building. Each row carries a verification step so you can confirm the capability is real.
AI Overview appearance varies by session, device, and geo. Capture it when it appears and log absence as a data point. SERP volatility is high on Mondays and after Google updates. A single day spike is rarely a story. Localization matters: gl and hl shift the results meaningfully, and mobile often diverges from desktop. Track brand and non-brand separately so brand wins do not mask non-brand losses. Pull paid and organic in the same fetch so the brief can speak to total visibility.
SerpAPI list-price plans on the public pricing page run from a free developer tier through Production and Big Data plans. Verify the current numbers on the pricing page before committing budget. The cost lever you actually control is cadence. Daily on the top fifty terms costs more than weekly on the same fifty. The point of a real pipeline is that you fetch only the SERPs you need on the cadence each keyword warrants. Claude tokens are a small share of the total cost because the prompt carries the diff and a small slice of SERP context.
Treat these as metrics to watch. Movement matters more than absolute values.
Rank trackers give you a number on a row. This pipeline gives you a written brief with intent shifts, new SERP features, and competitor moves. SEMrush and Ahrefs are strong reporting tools. The brief workflow lives at a different layer: you own the data, the prompt, and the output schema.
Check the SerpAPI pricing page for current list prices. The plan you need is a function of how many keywords, how many engines, and how often you fetch. The cost discipline that matters is fetching only the SERPs you need on the cadence each keyword warrants.
SerpAPI captures the AI Overview block when Google serves one. Appearance is variable across sessions and geos, so treat absence as a data point and store the raw block when it appears. Track the citation set as a first class signal because it tells you which sources Google trusts on the term.
Both work. For most analyses, pass the diff plus the top ten organic results and the AI Overview citations. For deep SERP analysis on a single term, pass the raw HTML and ask Claude to extract structure into JSON. Keep prompts tight and use a JSON output schema so the model returns facts you can store directly.
Daily on priority terms. Weekly on the long tail. The brief itself runs weekly because that matches how content and PR teams actually act on intel. Faster cadence on the brief without faster action just creates noise.
If you want SEO automation set up cleanly inside your content operations, let’s talk.