A page ranking #1 can get zero clicks if an AI Overview answers the query above it. Search surface monitoring tracks every SERP feature: AI Overviews, PAA boxes, snippets, knowledge panels, and local packs. When features change, your visibility changes even if rank stays the same. This tutorial builds a surface monitor at $0.005/keyword.
Prerequisites
- Python 3.8+
- requests library
- A Scavio API key from scavio.dev
- Keywords to monitor
Walkthrough
Step 1: Capture full SERP surfaces
Parse all feature types for each keyword.
import os, requests, json
from datetime import date
API_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
DOMAIN = 'mysite.com'
KEYWORDS = ['serp api python', 'web scraping api 2026', 'tiktok data api']
def capture(keyword):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=H, json={'query': keyword, 'country_code': 'us'}).json()
features = {}
if data.get('answer_box'): features['answer_box'] = True
if data.get('ai_overview'): features['ai_overview'] = True
if data.get('related_questions'): features['paa'] = len(data['related_questions'])
if data.get('knowledge_graph'): features['kg'] = True
pos = next((r['position'] for r in data.get('organic_results', []) if DOMAIN in r.get('link', '')), None)
return {'keyword': keyword, 'date': date.today().isoformat(), 'features': features, 'position': pos}Step 2: Compare against previous snapshots
Detect when features appear or disappear.
def detect_changes(current, previous):
if not previous: return ['NEW: first capture']
changes = []
curr_f = set(current['features'])
prev_f = set(previous.get('features', {}))
for f in curr_f - prev_f: changes.append(f'ADDED: {f}')
for f in prev_f - curr_f: changes.append(f'REMOVED: {f}')
cp, pp = current.get('position'), previous.get('position')
if cp and pp and cp != pp: changes.append(f'RANK: {pp} -> {cp}')
return changesStep 3: Run the monitor
Capture, compare, and save state.
def run_monitor():
try:
with open('surface.json') as f: prev = {e['keyword']: e for line in f for e in [json.loads(line)]}
except FileNotFoundError: prev = {}
surfaces = []
for kw in KEYWORDS:
s = capture(kw)
surfaces.append(s)
changes = detect_changes(s, prev.get(kw))
feats = ', '.join(s['features']) or 'organic only'
print(f' {kw}: pos={s["position"] or "N/A"}, [{feats}]')
for c in changes: print(f' {c}')
with open('surface.json', 'w') as f:
for s in surfaces: f.write(json.dumps(s) + '\n')
print(f'Cost: ${len(KEYWORDS) * 0.005:.3f}')
run_monitor()Step 4: Calculate visibility score
Estimate CTR impact from SERP features.
def visibility(surface):
pos = surface.get('position')
if not pos: return 0.0
base = {1: 0.30, 2: 0.15, 3: 0.10, 4: 0.07, 5: 0.05}.get(pos, 0.02)
if 'ai_overview' in surface.get('features', {}): base *= 0.5
if 'answer_box' in surface.get('features', {}): base *= 0.7
return round(base, 4)
for kw in KEYWORDS:
s = capture(kw)
v = visibility(s)
print(f' {kw}: pos={s["position"]}, visibility={v:.2%}')Python Example
import os, requests
API_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
def check(kw, domain):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=H, json={'query': kw, 'country_code': 'us'}).json()
features = [k for k in ['answer_box', 'ai_overview', 'related_questions', 'knowledge_graph'] if data.get(k)]
pos = next((r['position'] for r in data.get('organic_results', []) if domain in r.get('link', '')), None)
print(f'{kw}: pos={pos or "N/A"}, features={features}')
check('serp api python', 'mysite.com')JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
const H = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' };
async function check(kw, domain) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: H, body: JSON.stringify({ query: kw, country_code: 'us' })
}).then(r => r.json());
const features = ['answer_box', 'ai_overview', 'related_questions'].filter(k => data[k]);
const match = (data.organic_results || []).find(r => r.link.includes(domain));
console.log(`${kw}: pos=${match?.position||'N/A'}, features=[${features}]`);
}
check('serp api python', 'mysite.com').catch(console.error);Expected Output
serp api python: pos=4, [paa]
web scraping api 2026: pos=7, [ai_overview, paa, answer_box]
ADDED: ai_overview
RANK: 5 -> 7
tiktok data api: pos=2, [paa]
Cost: $0.015