Overview
You can rebuild a weekly competitor-research routine from public data with a single Scavio API key instead of paying for SimilarWeb, whose Web Intelligence Starter ran about $125/mo billed annually ($199 month-to-month) before pricing moved to quote-only in 2026. This workflow pulls each competitor's Google SERP positions, their Reddit mentions and scores, and their YouTube presence, then assembles a report that leaves unknown cells blank instead of inventing numbers. It covers public signals, not SimilarWeb's panel-based traffic estimates. If you specifically need modeled monthly visit counts, SimilarWeb still does that and this does not. The tradeoff is real: you get observable, citable facts (a rank you can screenshot, an upvote count you can click through to) instead of a single modeled traffic number, and for most positioning decisions the observable facts are the ones you can act on. One Scavio key drives all three platforms here. A weekly run across a handful of competitors and keywords costs a few credits per competitor at $0.005 each, so a full report is cents, not a $125 monthly line item.
Trigger
Weekly cron (e.g. Monday 6am)
Schedule
Weekly
Workflow Steps
Pull each competitor's SERP positions for your target keywords
For every competitor domain and every target keyword, POST to https://api.scavio.dev/api/v1/google with {"query": "...", "light_request": false} and an Authorization: Bearer header. Full SERP features return organic results, people_also_ask, knowledge_graph, and related_searches, which costs 2 credits per call (1 credit for a light request). Scan the organic array for the competitor's domain and record its rank. This is the one signal SimilarWeb never gave you cleanly: where a rival actually sits on the page for the queries you care about.
Pull Reddit mentions and scores to gauge community traction
POST each competitor name to https://api.scavio.dev/api/v1/reddit/search to count how often people mention them and how the threads score. A search costs 1 credit; pulling a full post with its comment tree costs 2. Upvotes and comment volume are a rougher signal than panel traffic, but they tell you whether a competitor has real word of mouth or just a marketing budget. A product with three dead threads is a different threat than one with a 400-upvote launch post.
Pull YouTube presence for share of voice
POST to https://api.scavio.dev/api/v1/youtube/search with {"search": "..."} (the YouTube endpoint uses search, not query) for each competitor. Count how many videos exist, who made them, and roughly how old they are. Heavy YouTube coverage usually means a competitor is investing in top-of-funnel demos and tutorials. Silence on YouTube is a gap you might fill yourself.
Assemble the report and leave gaps blank, do not invent numbers
Write one row per competitor with their best SERP rank, Reddit mention count, and YouTube video count. When a signal is missing, leave the cell empty. Do not estimate, do not interpolate, do not fill it with a plausible-looking number. The rule from the source thread holds: no model-generated numbers, because fake certainty is worse than an empty gap. A blank cell is honest and tells you exactly where to dig manually.
Diff against last week and alert on changes
Save each week's report as JSON. On the next run, compare the new numbers to last week's: a competitor that jumped from page 2 to position 3, a Reddit thread that suddenly scored 300, a burst of new YouTube videos. Send the diff to Slack or email so you read only what moved, not the whole table. Week-over-week deltas are where the useful decisions live.
Python Implementation
import os
import json
import requests
from datetime import date
API_KEY = os.environ["SCAVIO_API_KEY"]
BASE = "https://api.scavio.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
COMPETITORS = ["acme.com", "rivalcorp.com"]
KEYWORDS = ["project management software", "team task tracker"]
def google_rank(domain, keyword):
r = requests.post(f"{BASE}/google", headers=HEADERS,
json={"query": keyword, "light_request": False})
r.raise_for_status()
organic = r.json().get("organic", [])
for i, item in enumerate(organic, start=1):
if domain in item.get("link", ""):
return i
return None # not found in returned results; leave blank, do not guess
def reddit_mentions(name):
r = requests.post(f"{BASE}/reddit/search", headers=HEADERS, json={"query": name})
r.raise_for_status()
posts = r.json().get("posts", [])
return len(posts), sum(p.get("score", 0) for p in posts)
def youtube_count(name):
r = requests.post(f"{BASE}/youtube/search", headers=HEADERS, json={"search": name})
r.raise_for_status()
return len(r.json().get("videos", []))
def build_report():
rows = []
for c in COMPETITORS:
best_rank = None
for kw in KEYWORDS:
rank = google_rank(c, kw)
if rank is not None and (best_rank is None or rank < best_rank):
best_rank = rank
mentions, score_total = reddit_mentions(c)
videos = youtube_count(c)
rows.append({
"competitor": c,
"best_serp_rank": best_rank, # None means leave blank
"reddit_mentions": mentions,
"reddit_score_total": score_total,
"youtube_videos": videos,
})
return rows
def diff(today_rows, last_path="last_week.json"):
try:
with open(last_path) as f:
last = {r["competitor"]: r for r in json.load(f)}
except FileNotFoundError:
last = {}
for row in today_rows:
prev = last.get(row["competitor"])
if prev and prev["best_serp_rank"] != row["best_serp_rank"]:
print(f"ALERT {row['competitor']} rank {prev['best_serp_rank']} -> {row['best_serp_rank']}")
if prev and row["reddit_score_total"] - prev["reddit_score_total"] >= 200:
print(f"ALERT {row['competitor']} reddit score surged")
if __name__ == "__main__":
rows = build_report()
print(f"Competitor report {date.today().isoformat()}")
for r in rows:
rank = r["best_serp_rank"] if r["best_serp_rank"] is not None else "-"
print(f"{r['competitor']:<16} rank={rank} reddit={r['reddit_mentions']} (score {r['reddit_score_total']}) yt={r['youtube_videos']}")
diff(rows)
with open("last_week.json", "w") as f:
json.dump(rows, f, indent=2)
JavaScript Implementation
import fs from "node:fs";
const API_KEY = process.env.SCAVIO_API_KEY;
const BASE = "https://api.scavio.dev/api/v1";
const HEADERS = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
const COMPETITORS = ["acme.com", "rivalcorp.com"];
const KEYWORDS = ["project management software", "team task tracker"];
async function post(path, body) {
const res = await fetch(`${BASE}${path}`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${path} ${res.status}`);
return res.json();
}
async function googleRank(domain, keyword) {
const data = await post("/google", { query: keyword, light_request: false });
const organic = data.organic || [];
for (let i = 0; i < organic.length; i++) {
if ((organic[i].link || "").includes(domain)) return i + 1;
}
return null; // not found; leave blank, do not guess
}
async function redditMentions(name) {
const data = await post("/reddit/search", { query: name });
const posts = data.posts || [];
const score = posts.reduce((s, p) => s + (p.score || 0), 0);
return { mentions: posts.length, score };
}
async function youtubeCount(name) {
const data = await post("/youtube/search", { search: name });
return (data.videos || []).length;
}
async function buildReport() {
const rows = [];
for (const c of COMPETITORS) {
let bestRank = null;
for (const kw of KEYWORDS) {
const rank = await googleRank(c, kw);
if (rank !== null && (bestRank === null || rank < bestRank)) bestRank = rank;
}
const { mentions, score } = await redditMentions(c);
const videos = await youtubeCount(c);
rows.push({
competitor: c,
best_serp_rank: bestRank, // null means leave blank
reddit_mentions: mentions,
reddit_score_total: score,
youtube_videos: videos,
});
}
return rows;
}
function diff(today, lastPath = "last_week.json") {
let last = {};
try {
for (const r of JSON.parse(fs.readFileSync(lastPath, "utf8"))) last[r.competitor] = r;
} catch {}
for (const row of today) {
const prev = last[row.competitor];
if (prev && prev.best_serp_rank !== row.best_serp_rank) {
console.log(`ALERT ${row.competitor} rank ${prev.best_serp_rank} -> ${row.best_serp_rank}`);
}
if (prev && row.reddit_score_total - prev.reddit_score_total >= 200) {
console.log(`ALERT ${row.competitor} reddit score surged`);
}
}
}
const rows = await buildReport();
console.log(`Competitor report ${new Date().toISOString().slice(0, 10)}`);
for (const r of rows) {
const rank = r.best_serp_rank ?? "-";
console.log(`${r.competitor.padEnd(16)} rank=${rank} reddit=${r.reddit_mentions} (score ${r.reddit_score_total}) yt=${r.youtube_videos}`);
}
diff(rows);
fs.writeFileSync("last_week.json", JSON.stringify(rows, null, 2));