Claude does not have keyword data. Ask it for keywords and it predicts plausible-looking strings, it does not pull real search behavior. The fix every SEO on Reddit lands on is the same: give the model a data source. You can connect the Semrush MCP (mcp.semrush.com, requires a Pro plan at $139.95/mo) or the Ahrefs MCP (requires the Lite plan at $129/mo) for search volume. But for the SERP-side signals, who actually ranks, the People Also Ask questions, the related searches Google itself suggests, a SERP API is cheaper and gives the model real text to reason over. This tutorial wires Scavio's Google endpoint into a keyword-expansion loop. Honest scope: a SERP API does not return monthly search volume; for volume numbers you still want DataForSEO ($0.0006 per SERP, $50 minimum deposit) or Semrush. What it does return is the live SERP landscape, which is what makes the model's topic suggestions real instead of invented.
Prerequisites
- A Scavio API key (50 free credits on signup, no card)
- Python 3.9+ or Node 18+
- Basic familiarity with calling an LLM (Claude, GPT, or local)
Walkthrough
Step 1: Pull the live SERP for your seed keyword
Call the Google endpoint with light_request set to false. That returns organic results, the knowledge graph, People Also Ask, and related searches in one response. It costs 2 credits ($0.01) instead of 1, the extra blocks are exactly the keyword signals you want.
import os, requests
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
r = requests.post("https://api.scavio.dev/api/v1/google",
headers=H, json={"query": "serp api", "light_request": False}).json()
paa = [q["question"] for q in r["data"].get("people_also_ask", [])]
related = [s["query"] for s in r["data"].get("related_searches", [])]
print(paa, related)Step 2: Build a candidate list the model did not invent
People Also Ask gives you question-shaped long-tail keywords. Related searches give you lateral expansions Google associates with the seed. The top organic titles tell you the angle competitors already cover. Concatenate all three into a candidate pool, this is real data, not a guess.
titles = [o["title"] for o in r["data"]["organic_results"][:10]]
candidates = list(dict.fromkeys(paa + related + titles))Step 3: Hand the candidates to the model for clustering, not invention
Now the model has a job it is actually good at: grouping real queries into topic clusters and intent buckets. It is reasoning over data you fetched, so it cannot hallucinate a keyword that does not exist on the SERP. Prompt it to cluster by intent (informational, commercial, navigational) and flag gaps where no competitor title matches a PAA question.
prompt = f"""Cluster these real Google queries into topic groups by search intent.
Flag any People Also Ask question with no matching competitor title (content gap).
Queries: {candidates}"""
# send prompt to your LLM of choiceStep 4: Loop on the gaps to go deeper
Take each content-gap question, run it back through step 1 as a new seed, and you get a second ring of PAA and related searches. Two or three loops builds a full topic map grounded entirely in live SERP data. Cache the SERP responses for a day, search results for the same query rarely move within 24 hours, so you avoid re-spending credits.
Python Example
import os, requests
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
def serp_signals(seed):
r = requests.post("https://api.scavio.dev/api/v1/google",
headers=H, json={"query": seed, "light_request": False}).json()
d = r["data"]
paa = [q["question"] for q in d.get("people_also_ask", [])]
related = [s["query"] for s in d.get("related_searches", [])]
titles = [o["title"] for o in d.get("organic_results", [])[:10]]
return list(dict.fromkeys(paa + related + titles))
seeds = ["serp api"]
keyword_map = {s: serp_signals(s) for s in seeds}
print(keyword_map)JavaScript Example
const H = { Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`, "Content-Type": "application/json" };
async function serpSignals(seed) {
const res = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST", headers: H,
body: JSON.stringify({ query: seed, light_request: false }),
});
const { data } = await res.json();
const paa = (data.people_also_ask || []).map(q => q.question);
const related = (data.related_searches || []).map(s => s.query);
const titles = (data.organic_results || []).slice(0, 10).map(o => o.title);
return [...new Set([...paa, ...related, ...titles])];
}
console.log(await serpSignals("serp api"));Expected Output
A list of real query strings: PAA questions ("What is a SERP API used for?"), related searches ("free serp api", "google serp api python"), and the top 10 ranking titles, all pulled live from Google rather than predicted by the model.