Build Rankwise
KINDAreplaces $59/mosaves $708/yrback to the verdict
A local weekly citation tracker for one site across four answer engines, whose hard problems are an immutable run history, normalising citations so two spellings of one URL are one domain, and an on-page audit that gives correct advice about crawlers that do different jobs.
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 CLI and the local dashboard.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - API keypay per call, and it is the main running cost of this build
Why Each engine is a separate account with its own billing, and the search or grounding tool has to be enabled per provider or you are measuring the model's memory rather than its citations.
Get it Create an account and a key with each provider, then confirm the web search or grounding tool is available on your plan.
Verify
One question returns an answer with citations from each of the four - decidefree to decide
Why Questions times engines times weeks compounds quietly, and the ceiling is the only thing standing between a weekly job and a surprise invoice.
Get it Estimate one run at current published prices, decide what a week is worth, and write the figure into the configuration.
Verify
A run that would exceed the figure stops and says so - decidefree
Why The question set is the instrument, so changing it mid-history is like moving the goalposts and then measuring the distance.
Get it Write the questions a buyer actually types before they know your name, and commit to leaving them alone for a quarter.
Verify
The file loads and every question is answerable by a source you could plausibly be - installfree, or the cost of the machine
Why The value is the series, and a series with gaps where the laptop was shut is a series you cannot read.
Get it A cron entry on an always-on machine, or your platform's equivalent.
Verify
A scheduled run appears in the runs table without anyone pressing anything
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- site(domain, brand_aliases_json, competitor_domains_json) · one site, because the comparison only means anything for one - questions(id, text, added_at, retired_at) · retired rather than deleted, so old runs still make sense - runs(id, started_at, finished_at, questions_count, engines_json, estimated_cost, actual_cost, status) - answers(run_id, question_id, provider, model_id, search_enabled, raw_answer, latency_ms, error, created_at) · one immutable row per cell, errors included - citations(answer_id, ordinal, raw_url, host, canonical_path, is_mine, is_competitor) · normalised once at write time - audits(url, checked_at, findings_json) · the mechanical on-page checks, kept per run so a fix is visible as a change - No table in this schema has an update path. A correction is a new run, because the point of the history is that nobody edited it.
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 |
|---|---|---|---|
DATABASE_PATH | required | ./data/rankwise.db | Runs, answers and citations. Append-only and the whole asset. |
OPENAI_API_KEYsecret | required | sk-...redacted | With the web search tool enabled on the account. |
ANTHROPIC_API_KEYsecret | required | sk-ant-...redacted | With the web search tool enabled on the account. |
GEMINI_API_KEYsecret | required | AIza...redacted | With grounding enabled on the account. |
PERPLEXITY_API_KEYsecret | required | pplx-...redacted | Search is the default here, but record the flag anyway. |
RUN_COST_CEILING_USD | required | 12.00 | Checked before the fan-out. A run that would exceed it stops and records why. |
PROVIDER_CONCURRENCY | required | 3 | Per provider, not in total. One of them is always the slow one. |
RAW_ANSWER_RETENTION_DAYS | required | 400 | Raw answers are large. Scores and citations are kept forever regardless. |
The build, in order
A question set and a run that cannot be rewritten
One run writes one immutable row per question and engine, including the failures.
A trigger or a check that turns an update into a failed test rather than a lost week.
Files
src/db/schema.tstest/append-only.test.tsProviders change defaults underneath you and the comparison has to survive that.
Files
src/providers/types.tsAn empty cell is information; a missing row is an unanswered question about your own data.
Files
src/run.ts
done when · tick each as it passeswatch out- Storing one current score per question and updating it each week feels tidier and destroys the only thing this tool is for, because a citation rate in week one means nothing and a trend over six months means everything. Append a row per run and never update one.
Fanning out without burning the budget
Four providers are called concurrently under a spend ceiling with visible failures.
Each has its own limits and one of them is always the slow one.
Files
src/pool.tsThe estimate comes from the current published prices, which you re-check, not from a constant.
Files
src/budget.tsdocs/costs.mdA failed cell with an error beats a run that never ends.
Files
src/retry.tstest/retry.test.ts
done when · tick each as it passeswatch out- Retrying a rate limit immediately in a tight loop converts one throttled provider into a run that takes an hour and costs twice the estimate, and retrying a request that already produced an answer pays for the same cell twice. Back off, cap attempts, and record a cell as failed rather than retrying it forever.
Two spellings of one URL are one citation
Citations normalise to a host and a canonical path so counting means something.
Normalising at read time means every report defines its own truth.
Files
src/normalise.tstest/normalise.test.tsWhen a count looks wrong the raw string is the only way to find out why.
Files
src/db/schema.tsBeing named and being linked are different outcomes and mixing them flatters you.
Files
src/score.ts
done when · tick each as it passeswatch out- Counting citations by exact URL string splits one page into five entries because of a trailing slash, a tracking parameter, an http scheme and a www prefix, and your own site then appears to be cited a fifth as often as it is. Normalise at write time, keep the raw URL beside it, and test the five variants collapse to one.
An audit that gives correct crawler advice
The on-page audit reports mechanical facts and names the right agent for the right job.
Training, search, ads and user-initiated fetching are four different decisions.
Files
src/audit/crawlers.tsdocs/crawlers.mdA malformed block is worse than none and looks identical from the outside.
Files
src/audit/jsonld.tsNobody can promise a citation, and a tool that implies it is lying politely.
Files
src/audit/report.ts
done when · tick each as it passeswatch out- Reporting 'GPTBot is blocked, so you are invisible in ChatGPT' is wrong advice, and it is the single most common mistake in this category. OpenAI documents four separate agents with different jobs: GPTBot is the model training crawler, OAI-SearchBot is what surfaces sites in ChatGPT's search features, OAI-AdsBot validates pages submitted as ads, and ChatGPT-User serves user-initiated actions where, in OpenAI's own words, robots.txt rules may not apply. Report each token separately, say what blocking it actually costs, and re-read the vendor pages before you ship, because these tokens are new and they move.
History you can read and take with youproduct builder
The dashboard shows trend and gaps, and everything exports without the app.
The provider swapping a model is the most common false signal in this whole category.
Files
src/dashboard/diff.tsA rate from two runs is a coin toss with a percentage sign.
Files
src/dashboard/views.tsThe history is the asset, so it must leave the tool intact.
Files
src/export.ts
done when · tick each as it passeswatch out- Showing a week over week movement without showing that the provider changed model version in between invites a team to celebrate or panic about a change they did not cause. Mark every diff whose model identifier differs from the previous run, and say plainly that the two weeks are not comparable.
That is the whole plan for Rankwise. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Article drafting and CMS publishing.
- Any scripting of the consumer chat interfaces.
- A single blended visibility score.
- Competitor site-wide tracking.
- the CMS publishing matrix, and the upkeep as WordPress, Shopify, Webflow and Wix each change their APIs
- the editorial layer: topics ranked by demand, citation gap and winnability, then drafted and reviewed rather than dumped
- exact technical diffs (JSON-LD, canonicals, robots rules, llms.txt) instead of generic advice you still have to translate
- months of score history, without which a single week's citation rate tells you nothing
- per-engine weekly scoring across seven on-page dimensions, which costs real API spend every run
- A fifth engine added as an adapter, once the four have run for a quarter without a gap
- Per-question cost reporting, so an expensive question with no movement can be retired honestly
- An annotation layer marking the dates you published something, since a trend with no events is a line
Need the files? The project pack on the verdict page hands your agent the whole brief · more seo & marketing.