Build Rankhog
KINDAreplaces $99/mosaves $1,188/yrback to the verdict
A local opportunity finder whose hard problems are being a well-behaved API client without hard-coding a rate you cannot verify, spending a paid search budget only on threads worth checking, and drafting replies that answer a question rather than advertising.
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 sweep and the local review page.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - accountfree for personal, non-commercial use; commercial use needs separate approval
Why The API expects an identified client, and the terms you agree to when registering are the ones that govern what you may do with the data.
Get it Create an application in your Reddit preferences, note the client identifier and secret, and read the current data API terms and developer guidance before writing the client.
Verify
An authenticated search returns results and the response carries rate limit headers you can read - API keya few pounds per thousand queries on most providers
Why The ranking filter is the thing that separates a thread worth answering from one nobody will ever see, and it is the only per-query cost in the build.
Get it Any search API with published per-query pricing and a spend cap you can set.
Verify
One keyword lookup returns results and the account shows the queries used - API keypennies per sweep at a few dozen drafts
Why Drafting is the other external cost, and it should have its own limit rather than sharing one.
Get it Create a key with its own spend limit.
Verify
A draft returns and the token usage is recorded - have readyfree, and expensive in time
Why This is the part the tool cannot give you, and the reason the paid product has a business. Without it the best draft in the world gets removed.
Get it Comment usefully, without mentioning anything you sell, for several weeks. There is no shortcut and buying one is worse than nothing.
Verify
You have posted ordinary comments in each target subreddit over multiple weeks - decidefree
Why Each is enforced by code and asserted by a test, and choosing them after a bad week means choosing whatever already happened.
Get it Write down the paid queries per sweep, the promotional replies per subreddit per week, and the ordinary to promotional ratio below which the tool stops suggesting promotion.
Verify
All three are in the configuration and named in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- keywords(id, text, subreddits_json, enabled, added_at) - threads(id, remote_id, subreddit, title, permalink, author, created_at, score, num_comments, fetched_at) - matches(id, thread_id, keyword_id, matched_at, state) · state is new, ranked, drafted, dismissed or posted_by_me - serp_cache(keyword_id, checked_at, results_json, queries_used) · one lookup per keyword per window, reused by every match - sub_rules(subreddit, rules_json, fetched_at, self_promo_summary) · pulled from the subreddit's own rules endpoint, refreshed on a schedule - drafts(match_id, model, prompt_version, text, created_at, edited_text, copied_at) - participation(subreddit, week, ordinary_comments, promotional_comments) · you fill this in, and the tool refuses to suggest more promotion when the ratio is wrong - Dismissal is permanent. A thread you rejected never appears again, because a finder that repeats itself is noise.
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/rankhog.db | Threads, matches, drafts and dismissals. The dismissals are the part you cannot rebuild. |
REDDIT_CLIENT_IDsecret | required | redacted | From your registered application. Never in the repository. |
REDDIT_CLIENT_SECRETsecret | required | redacted | Same application. Rotate it if it ever reaches a log. |
REDDIT_USER_AGENT | required | rankhog/0.1 by u-yourname (contact: you@example.com) | Names the tool, a version and a contact, as the platform's guidance asks. |
SERP_API_KEYsecret | required | redacted | The ranking filter. Set a spend cap at the provider as well as here. |
SERP_QUERIES_PER_SWEEP | required | 25 | A hard stop. The sweep reports what it did not check. |
LLM_API_KEYsecret | required | sk-...redacted | Drafting only. Its own key with its own limit. |
PROMO_PER_SUB_PER_WEEK | required | 1 | Above this, promotional drafts are hidden and the reason is shown. |
The build, in order
A client the platform can identify and slow down
Every request is authenticated, identifiable, and paced by what the server says rather than by a number you typed.
Read the current terms before you write the client, not after it is blocked.
Files
src/reddit/client.js.env.exampleThe server already tells you the answer on every reply.
Files
src/reddit/pacing.jstests/pacing.test.jsA retry loop through a 429 is the behaviour that earns a block.
Files
src/reddit/client.js
done when · tick each as it passeswatch out- Copying a requests-per-minute figure out of a blog post is wrong twice: the figure is stale, and it ignores that the server reports your remaining allowance and reset time on every single response. Pace from those headers, and keep no rate constant in the code at all so there is nothing to go out of date.
The sweep, and the pile you already rejected
A sweep turns keywords into candidate threads without ever resurfacing one you dismissed.
Everything free happens before anything billed.
Files
src/filters.jstests/filters.test.jsThe dismissed pile is what makes the list readable on day ten.
Files
src/matches.jsA keyword that only produces noise should be deleted, not endured.
Files
src/sweep.js
done when · tick each as it passeswatch out- Storing matches without a dismissal state means every sweep hands you the same forty threads you looked at yesterday, plus two new ones, and you stop reading the list by the fourth day. Make dismissal permanent, show new matches first, and count how many candidates each keyword produced so a keyword that only generates noise is obvious.
Only the threads people actually land on
A paid search lookup happens once per keyword per window and the budget is a hard stop.
The ranking question belongs to the keyword.
Files
src/serp.jstests/serp-cache.test.jsA soft budget is a bill with an apology attached.
Files
src/budget.jsA cost you can see is a cost you can decide about.
Files
src/views/sweep.ejs
done when · tick each as it passeswatch out- Checking every matched thread against the search API spends the budget on threads nobody ever sees, because the ranking question is about the keyword, not the thread: one lookup per keyword answers it for every match at once. Cache per keyword per window, and make the budget a hard stop that reports the remainder instead of a soft target that gets exceeded.
Drafts that answer the question
Every draft opens with a real answer and respects the subreddit's own rules about promotion.
And show them beside the draft, with the fetch time.
Files
src/rules.jssrc/draft.jsThat property is the difference between help and spam.
Files
src/draft.jstests/answer-first.test.jsThere is no send. That is the point.
Files
src/views/match.ejs
done when · tick each as it passeswatch out- A model told to write a reply promoting your product writes an advertisement with a question mark on the end, and that is precisely the comment that gets removed, reported, and remembered. Put the subreddit's own rules in the prompt, require the answer to stand on its own if the last sentence were deleted, and check that property before showing the draft.
The pace that keeps the account aliveproduct builder
The tool refuses to suggest more promotion than the account has earned.
A visible cap you set beats a burst you regret.
Files
src/pacing.jstests/promo-cap.test.jsIt is the number moderators are actually judging.
Files
src/participation.jsIncluding dismissals, which are the part you cannot rebuild.
Files
src/export.js
done when · tick each as it passeswatch out- The finder turns up forty good threads in its first week, you reply to all forty from an account three weeks old, and that burst is the exact pattern enforcement is tuned for; the account goes quiet and you cannot tell, because a shadowban looks normal from the inside. Cap promotional suggestions per subreddit per week and make the cap visible rather than advisory.
That is the whole plan for Rankhog. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Any posting, commenting, voting or login.
- Multiple or purchased accounts.
- Promises about AI answer citations.
- Commercial use of the platform's data without approval.
- account age, karma, and comment history in the subs that matter
- the warm-up: weeks of ordinary participation before you can mention a product
- posting through a real browser session rather than the API, which is what keeps accounts unflagged
- per-subreddit rule knowledge and pacing judgment
- shared accounts across a team, and the managed service
- A second source of threads, such as a forum with an open API, added behind the same filters
- A weekly digest of new matches by email, reusing the sweep output
- Tracking which of your posted replies earned replies, entered by hand and shown against the drafts
Need the files? The project pack on the verdict page hands your agent the whole brief · more seo & marketing.