Build Surfer SEO
KINDAreplaces $99/mosaves $1,188/yrback to the verdict
Fetch the pages currently ranking for a query through a search data provider, extract their structure and vocabulary, produce a brief covering the subtopics they all address, score a draft against that coverage as a checklist, and record what actually happened to the page afterwards so the score can be judged rather than trusted.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the fetching and analysis.
Get it python.org or your package manager, then a virtual environment.
Verify
python3 --version prints 3.12 or higher - API keypriced per call; a few hundred queries a month is typically tens of US dollars and position tracking multiplies it
Why This is the input and it has no free substitute. Scraping is against terms, unreliable and blocked, which makes every downstream number meaningless.
Get it Choose a provider, read its per-call pricing carefully, and start on the smallest plan. Estimate your monthly calls from briefs plus position tracking before committing.
Verify
A query returns ranked results with urls - have readyfree
Why Phase 5 evaluates whether any of this works, and it needs pages whose positions you can actually track. Without them the tool cannot be judged.
Get it Use pages you have already published. If you have none, this build cannot be evaluated yet and you should know that before spending on search data.
Verify
Ten pages with target queries are recorded - decidefree
Why The most likely finding is that coverage score and outcome are weakly related. A tool built to confirm a belief is worse than no tool.
Get it Decide before Phase 5 that you will act on the finding either way, and write that down.
Verify
The commitment is recorded before outcomes are measured - decidethe cap you choose
Why Per-call pricing plus weekly position tracking grows faster than expected, and the point of leaving a subscription is spending less.
Get it Set a figure and a hard cap at the provider before the first bulk run.
Verify
A provider spend cap is configured
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- queries(id, text, locale, device, checked_at) - serp_results(query_id, position, url, title, fetched_at) - pages(id, url, fetched_at, word_count, headings_json, text, status) - terms(page_id, term, count) · after normalisation, for comparison rather than for display - subtopics(query_id, name, present_in_count, example_headings_json) · what most ranking pages cover - briefs(id, query_id, created_at, subtopics_json, target_words, notes) - drafts(id, brief_id, text, scored_at, coverage_json, score) - outcomes(page_url, query_id, checked_on, position) · what happened after publishing, which is the only real evidence
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
SERP_API_KEYsecret | required | replace-with-provider-key | Your search data provider. Priced per call, so treat it as a spending instrument. |
SERP_CACHE_HOURS | required | 168 | How long a query's results are reused. Rankings do not change hourly and calls cost money. |
FETCH_TIMEOUT_MS | required | 8000 | Per-page timeout when reading ranking pages. |
FETCH_USER_AGENT | required | ContentBrief/1.0 (contact: you@example.com) | Identify yourself honestly. You are reading other people's sites. |
TRACKING_INTERVAL_DAYS | required | 7 | How often positions are checked. This is the cost that grows quietly. |
MONTHLY_SPEND_CAP_USD | required | 30 | Stop making billed calls at this figure. Mirror it as a hard cap at the provider. |
The build, in order
Search data from a supportable source
You can fetch what currently ranks for a query, repeatedly, without being blocked.
With locale and device. Read the pricing before building, because this is the recurring cost.
Files
src/serp.pyThese calls are priced individually and rankings do not change hourly.
Files
src/cache.pySo the eventual comparison against the subscription is real rather than a feeling.
Files
src/cost.py
done when · tick each as it passeswatch out- Scraping search result pages directly is against the terms of every major engine, returns different results for automated clients, and gets blocked. It also produces data you cannot rely on, which makes every downstream analysis meaningless. Pay for the API or accept that the build has no input.
Fetching and extracting the ranking pages
You have the text and structure of the pages you are comparing against.
Timeout, byte cap, rate limit, honest user agent. You are reading someone's site.
Files
src/fetch.pyThe hierarchy matters as much as the words for a brief.
Files
src/extract.pyNever treat a failure as empty. Report the analysed count on the brief.
Files
src/extract.py
done when · tick each as it passeswatch out- Treating a failed fetch as an empty page skews every average downward and makes the target word count nonsense. Exclude failures from the analysis and report how many of the ranking pages you actually managed to read, because a brief built from three of ten pages is a different thing.
Subtopics, which are more useful than terms
The brief says what the ranking pages all cover, which is the part worth acting on.
Across pages, grouping equivalent headings. Report coverage counts rather than a flat list.
Files
src/subtopics.pyWord count as a range and median from analysed pages only.
Files
src/brief.pyExample headings per subtopic, so the writer can judge relevance rather than obeying.
Files
src/brief.py
done when · tick each as it passeswatch out- A single target word count implies a precision that does not exist, and writing to it produces padding. Report the range and the median across the pages you actually analysed, and let the writer decide, since covering the subject well is the thing that correlates with the number rather than the reverse.
Scoring a draft, honestly labelled
A draft gets a coverage checklist rather than a number to maximise.
Subtopics addressed and omitted. That is the actionable output.
Files
src/score.pyAvailable, labelled, not the headline. Term matching is a weak proxy and should look like one.
Files
src/score.pyNothing in the interface should imply a ranking outcome. Check the strings deliberately.
Files
src/ui.py
done when · tick each as it passeswatch out- A single score invites optimisation, and optimising term frequency produces text that reads as though it was written for a machine, which is the outcome search engines have spent two decades getting better at detecting. Lead with coverage, keep terms secondary, and never phrase the score as a likelihood of ranking.
What actually happened
You record where your published pages ended up, so the tool can be judged.
On a schedule, through the same paid API, with the cost accounted for.
Files
src/tracking.pyAt a sample size where the comparison means something. Ten pages is a minimum, not a good number.
Files
src/evaluate.pyIt is the most likely finding and the most useful one.
Files
src/evaluate.py
done when · tick each as it passeswatch out- Without outcome tracking this tool cannot be evaluated and you will keep using it because it feels productive. Record positions over time, and be prepared for the answer that coverage score and outcome are barely related, because that is a genuinely possible and useful finding.
Cost, and whether this was worth itproduct builder
You can say what this costs and whether to keep it.
Briefs and tracking separately. Tracking is the one that grows quietly.
Files
src/cost.pyCheaper and useless is not a win. Both halves belong in the comparison.
Files
docs/comparison.mdIncluding the case where you go back. A documented negative result is worth more than a quiet retreat.
Files
docs/comparison.md
done when · tick each as it passeswatch out- Search data pricing is per call and briefs plus position tracking add up faster than people expect, particularly once you track many queries weekly. Total it monthly against the subscription, because the build being free is the assumption that usually turns out wrong.
That is the whole plan for Surfer SEO. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Search result data of your own.
- The commercial scoring model.
- The editor experience.
- Any causal claim about ranking.
- SERP data reliability
- scoring model
- editor UX
- AI article workflows
- integrations
- audits
- support
- Tracking competitor pages for the same queries, which explains movement better than your own page alone
- A content refresh queue for pages whose position has decayed
- Comparing briefs against outcomes by subtopic, which is the analysis that would actually teach you something
Need the files? The project pack on the verdict page hands your agent the whole brief · more seo & marketing.