Overview
The fastest ranking wins are the keywords Google already shows your pages for on page 3 or 4, because the intent match is proven and you only need to close a gap, not earn relevance from zero. Google Search Console tells you which queries you get impressions for at position 11-40; those are striking-distance keywords. The missing half, which the r/DoSEO thread kept asking about, is what to do next: adding the keyword as an H2 rarely moves it. This workflow pulls the striking-distance list from GSC, then uses a SERP API to see exactly who occupies page 1 for each query and what their pages cover, so the update you make is grounded in the actual competition instead of a guess. Run it every two weeks and work the list top-down by impressions.
Trigger
Scheduled: every 2 weeks, or after any significant content update
Schedule
Biweekly (cron: 0 9 */14 * *)
Workflow Steps
Pull striking-distance queries from GSC
Query the Search Console API for the last 28 days, dimension = query, and keep rows where average position is between 11 and 40 with meaningful impressions (say 50+). These are keywords Google already thinks you're relevant for. Sort by impressions descending; the top of that list is where a page-3 to page-1 move earns the most clicks.
See who owns page 1 for each query
For every striking-distance query, call Scavio's Google endpoint with light_request: false. You get the top organic results plus people-also-ask and related searches. The PAA and related-searches blocks are the gap map: they show the subtopics the page-1 results satisfy that your page probably doesn't. This is the step that turns 'add the keyword somewhere' into 'cover these three subquestions the winners answer and you don't.'
Diff your page against the page-1 coverage
Compare the subtopics surfaced by PAA/related-searches and the page-1 result titles against your existing page. Flag the concepts you're missing. If three of the top five results answer 'X vs Y' and your page never mentions Y, that's a concrete section to add, not a keyword to sprinkle.
Update the page for coverage, not density
Rewrite to genuinely answer the missing subquestions with real substance: a comparison, a code sample, a decision rule. Keyword-stuffing an H2 is what the thread correctly said doesn't work. Depth on the sub-intents is what moves a position-15 page toward the top. Keep one page per intent to avoid cannibalizing yourself across near-duplicate URLs.
Re-measure in the next cycle
Two weeks later, re-pull GSC and check whether the updated queries moved up and started converting impressions into clicks. Keep the ones that moved, re-diff the ones that stalled (usually a deeper gap or a stronger incumbent), and add the newly-surfaced striking-distance queries to the list.
Python Implementation
import os, requests
SCAVIO = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}",
"Content-Type": "application/json"}
def striking_distance(gsc_rows):
"""gsc_rows: list of {'query','position','impressions'} from the
Search Console API. Keep position 11-40 with 50+ impressions."""
rows = [r for r in gsc_rows
if 11 <= r["position"] <= 40 and r["impressions"] >= 50]
return sorted(rows, key=lambda r: -r["impressions"])
def page1_gap(query):
r = requests.post("https://api.scavio.dev/api/v1/google", headers=SCAVIO,
json={"query": query, "light_request": False}, timeout=30).json()
return {
"query": query,
"page1": [o["title"] for o in r.get("organic", [])[:5]],
"also_asked": [q.get("question") for q in r.get("people_also_ask", [])],
"related": r.get("related_searches", []),
}
def build_worklist(gsc_rows):
return [page1_gap(row["query"]) for row in striking_distance(gsc_rows)[:25]]
if __name__ == "__main__":
demo = [{"query": "serp api for agents", "position": 17, "impressions": 420}]
for item in build_worklist(demo):
print(item)JavaScript Implementation
const SCAVIO = { Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
"Content-Type": "application/json" };
function strikingDistance(rows) {
return rows
.filter((r) => r.position >= 11 && r.position <= 40 && r.impressions >= 50)
.sort((a, b) => b.impressions - a.impressions);
}
async function page1Gap(query) {
const r = await fetch("https://api.scavio.dev/api/v1/google", {
method: "POST", headers: SCAVIO,
body: JSON.stringify({ query, light_request: false }),
}).then((r) => r.json());
return {
query,
page1: (r.organic || []).slice(0, 5).map((o) => o.title),
alsoAsked: (r.people_also_ask || []).map((q) => q.question),
related: r.related_searches || [],
};
}
async function buildWorklist(gscRows) {
const list = strikingDistance(gscRows).slice(0, 25);
return Promise.all(list.map((row) => page1Gap(row.query)));
}Platforms Used
Web search with knowledge graph, PAA, and AI overviews