Finding guest-post targets at scale is a search-footprint problem: you run dozens of "write for us" and intitle: queries across your niche, dedupe the domains, and hand the survivors to a human for quality judgment. A popular r/SEO post nailed the real constraint - vetting 1,000+ sites by hand because Google's own UI exhausts a footprint fast and scraping it needs a paid API. This tutorial automates the discovery half with the Scavio Google endpoint and is honest about the half you cannot automate: the quality call still needs a human. Five steps, runnable code.
Prerequisites
- Python 3.8+ or Node 18+ installed
- A Scavio API key from scavio.dev (50 free credits on signup)
- requests installed (pip install requests) for Python
- A target niche and 2-3 seed terms (e.g. "travel", "fintech", "home decor")
Walkthrough
Step 1: Build the footprint query set
Guest-post pages share predictable footprints. Combine your niche with patterns like 'write for us', 'guest post guidelines', 'contribute', 'become a contributor', and intitle: variants. The wider the footprint set, the less you exhaust any single query - which was the exact pain in the r/SEO thread.
NICHE = "home decor"
FOOTPRINTS = [
f'{NICHE} "write for us"',
f'{NICHE} "guest post guidelines"',
f'{NICHE} intitle:"write for us"',
f'{NICHE} "become a contributor"',
f'{NICHE} inurl:guest-post',
]Step 2: Run each query through the SERP endpoint
POST each footprint to https://api.scavio.dev/api/v1/google. A light request (1 credit) is enough here - you only need organic URLs, not the knowledge graph. Page through results to widen coverage. Bearer auth, structured JSON back, no proxies.
import os, requests
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
def search(q):
r = requests.post("https://api.scavio.dev/api/v1/google",
headers=H, json={"query": q})
r.raise_for_status()
return r.json().get("data", {}).get("organic", [])Step 3: Extract and dedupe by registered domain
Different footprints return the same sites, so dedupe by domain, not URL. Strip to the registrable domain and keep a set. This is where a raw Google UI session falls apart and a script shines - you collapse hundreds of overlapping results into a clean prospect list.
from urllib.parse import urlparse
def domain(url):
host = urlparse(url).netloc.lower()
return host[4:] if host.startswith("www.") else host
def collect(footprints):
seen = set()
for q in footprints:
for res in search(q):
d = domain(res.get("link", ""))
if d:
seen.add(d)
return sorted(seen)Step 4: Filter out the obvious junk automatically
Drop domains you never want: your own competitors' blogs, known link farms, and giant platforms (medium.com, linkedin.com). A small blocklist plus a check that the page title actually contains a contributor phrase removes most noise before a human ever looks. Automate the filtering, not the judgment.
BLOCKLIST = {"medium.com", "linkedin.com", "quora.com", "reddit.com"}
def prospects(footprints):
domains = collect(footprints)
return [d for d in domains if d not in BLOCKLIST and "." in d]Step 5: Hand the survivors to a human - this is the honest limit
The script gives you a deduped, filtered prospect list in minutes instead of days. It cannot judge whether a site's audience, authority, and 'look and feel' are worth pitching - that is human work, exactly as the r/SEO poster argued. Export to CSV, then have a person score each domain before outreach. Treat the API as a discovery accelerator, not a replacement for judgment.
import csv
rows = prospects(FOOTPRINTS)
with open("guest_post_prospects.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["domain", "human_score_0_5", "notes"])
for d in rows:
w.writerow([d, "", ""])
print(f"{len(rows)} prospects exported for manual review")Python Example
import os, requests
from urllib.parse import urlparse
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}", "Content-Type": "application/json"}
NICHE = "home decor"
FOOTPRINTS = [f'{NICHE} "write for us"', f'{NICHE} intitle:"write for us"', f'{NICHE} "become a contributor"']
BLOCKLIST = {"medium.com", "linkedin.com", "reddit.com"}
def domain(u):
h = urlparse(u).netloc.lower()
return h[4:] if h.startswith("www.") else h
seen = set()
for q in FOOTPRINTS:
r = requests.post("https://api.scavio.dev/api/v1/google", headers=H, json={"query": q})
for res in r.json().get("data", {}).get("organic", []):
d = domain(res.get("link", ""))
if d and d not in BLOCKLIST:
seen.add(d)
print(sorted(seen))JavaScript Example
const H = { Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`, 'Content-Type': 'application/json' };
const NICHE = 'home decor';
const FOOTPRINTS = [`${NICHE} "write for us"`, `${NICHE} intitle:"write for us"`, `${NICHE} "become a contributor"`];
const BLOCKLIST = new Set(['medium.com', 'linkedin.com', 'reddit.com']);
const domain = u => { const h = new URL(u).hostname.toLowerCase(); return h.startsWith('www.') ? h.slice(4) : h; };
const seen = new Set();
for (const q of FOOTPRINTS) {
const r = await fetch('https://api.scavio.dev/api/v1/google', { method: 'POST', headers: H, body: JSON.stringify({ query: q }) }).then(x => x.json());
for (const res of (r.data?.organic || [])) {
try { const d = domain(res.link); if (d && !BLOCKLIST.has(d)) seen.add(d); } catch {}
}
}
console.log([...seen].sort());Expected Output
['decorhubblog.com', 'homestylejournal.com', 'interiorinsider.net', ...]
# A deduped, junk-filtered list of guest-post prospect domains for your niche,
# ready for the part no API can do: a human scoring each site's authority and fit.