This builds a content brief straight from the live SERP: pull the top-ranking results, People Also Ask, and related searches for a keyword, then hand that structure to Claude and get back an entity list and an H2 outline. It automates the manual version a lot of SEO consultants describe on Reddit, reading the top 10-15 URLs, noting the shared subtopics, and flagging gaps, without you copy-pasting SERPs by hand. One honest limit up front: a SERP API returns the search page (titles, snippets, PAA, related searches), not the full body of each ranking page. That is usually enough to map what subtopics the SERP rewards. If you want entities pulled from the full article bodies, add a page-fetch step after step 2. Google full results cost 2 credits on Scavio ($0.005/credit).
Prerequisites
- A Scavio API key (50 free signup credits are enough to test this)
- An Anthropic API key for Claude
- Python 3.10+ or Node 18+ with an HTTP client
Walkthrough
Step 1: Pull the full SERP for your target keyword
Call the Google endpoint with light_request set to false so the response includes People Also Ask and related searches, not just organic links. Those two blocks are the cheapest signal for what the SERP expects a page to cover.
import os, requests
API = "https://api.scavio.dev/api/v1/google"
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
# light_request=False adds related_questions + related_searches + knowledge_graph (2 credits)
r = requests.post(API, headers=H, json={"query": "best crm for real estate agents", "light_request": False})
data = r.json()
organic = data["organic_results"] # ranked list: title, link, snippet
paa = data.get("related_questions", []) # People Also Ask: the questions the SERP rewards
related = data.get("related_searches", []) # each item has a "query" fieldStep 2: Extract the ranking set and the question blocks
Take the top 10-15 organic results (title plus snippet), every People Also Ask question, and the related searches. Together these are your raw entity and subtopic map, the same inputs a manual brief would collect.
top = organic[:15]
brief_input = {
"keyword": "best crm for real estate agents",
"ranking_titles": [o["title"] for o in top],
"ranking_snippets": [o.get("snippet", "") for o in top],
"paa": [q["question"] for q in paa],
"related": [s["query"] if isinstance(s, dict) else s for s in related],
}Step 3: Ask Claude to find the shared entities and the gaps
Send the structured SERP to Claude with a prompt that asks for entities and subtopics common across the ranking set, plus what is under-covered. Keep the prompt specific; a vague prompt gets you a generic outline.
import anthropic, json
client = anthropic.Anthropic()
prompt = (
"You are an SEO strategist. Below is the live Google SERP for a keyword: "
"ranking titles and snippets, People Also Ask, and related searches. "
"1) List the entities and subtopics shared across the ranking pages. "
"2) Flag subtopics that appear in PAA or related searches but are thin in the "
"ranking titles (content gaps). 3) Return an H2 outline that covers both.\n\n"
+ json.dumps(brief_input, indent=2)
)
msg = client.messages.create(
model="claude-sonnet-5", max_tokens=1500,
messages=[{"role": "user", "content": prompt}],
)
print(msg.content[0].text)Step 4: Optional: enrich with full page bodies
If snippet-level entities are not enough, fetch each ranking URL's body with your own crawler or fetch step and add the extracted text to brief_input before the Claude call. This gets you article-level entities at the cost of a slower run.
# add a fetched-body field per URL, then re-run the Claude step
brief_input["ranking_bodies"] = [fetch_text(o["link"]) for o in top] # your fetcherPython Example
import os, requests
API = "https://api.scavio.dev/api/v1/google"
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
# light_request=False adds related_questions + related_searches + knowledge_graph (2 credits)
r = requests.post(API, headers=H, json={"query": "best crm for real estate agents", "light_request": False})
data = r.json()
organic = data["organic_results"] # ranked list: title, link, snippet
paa = data.get("related_questions", []) # People Also Ask: the questions the SERP rewards
related = data.get("related_searches", []) # each item has a "query" fieldJavaScript Example
const H = {
Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
"Content-Type": "application/json",
};
const res = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST",
headers: H,
body: JSON.stringify({ query: "best crm for real estate agents", light_request: false }),
});
const data = await res.json();
const organic = data.organic_results;
const paa = data.related_questions ?? []; // People Also AskExpected Output
The Google call returns organic_results (title, link, snippet), related_questions (the People Also Ask blocks), and related_searches. Claude returns three sections: a shared-entity list, a content-gap list built from PAA and related searches, and an H2 outline. The whole run is one Google request (2 credits) plus one Claude call per keyword.