What one Google rank check actually costs
I sell rank tracking for $5 a month, which means I had to answer a question most people building on top of an API never bother with: what does one check actually cost me?
The answer turned out not to be a number. It's a set of decisions I make in code, and each of them moves the cost by a factor of several. That's worth writing down, because I'd assumed the unit cost was something I'd look up rather than something I'd choose.
The unit changed in September 2025
Google removed &num=100, the parameter that let a single request
return a hundred organic results. Every SERP API that quietly depended on it now
returns about ten results per request and pages for the rest.
So the cost of finding out where a domain ranks is no longer one call. It's however many pages you have to walk before you find it. My provider bills roughly one credit per ten-result page, so the arithmetic is one credit per page fetched.
That single change turns the pricing question into a search problem, and search problems have early exits.
Your cheapest keywords are the ones you rank for
Here's the shape of the loop that does the work. It pages forward, counting organic
results cumulatively — because the provider's per-page position resets
to 1 on every page, so the absolute rank is the running index, not what the API
hands you:
for page in range(1, pages + 1):
organic, cached = self._fetch_cached(keyword, gl, hl, page)
if not cached:
fetched += 1
...
if depth_reached or position is not None:
break
The break is the interesting line. Once the domain is found, there is
no reason to keep paging — the top-ten competitor list was already captured on page
one, and the only remaining job was locating the domain.
So the cost per keyword is bimodal, not average:
- Ranking at #7? One page. One credit.
- Ranking at #45? Five pages. Five credits.
- Not ranking within the depth I look? The full depth, every time.
Which is a slightly uncomfortable incentive: the keywords that cost me the most are the ones where the customer is doing worst. A user tracking five terms they already rank for is nearly free. A user tracking five aspirational terms they'll never crack is my most expensive account, forever, and they're the one most likely to churn.
Depth is the dial, and it belongs to the plan
Because not-found keywords cost the full walk, the depth I search is the single biggest cost decision in the product. It's a column on the plan row, not a global constant:
| Plan | Search depth | Max credits per keyword | Monthly quota |
|---|---|---|---|
| Free | 10 | 1 | 150 |
| Solo ($5) | 30 | 3 | 900 |
| Pro ($10) | 50 | 5 | 3,600 |
A depth of 10 on the free plan isn't stinginess for its own sake — it makes the worst case computable. One page maximum means one credit maximum, which means a free account cannot cost me more than its keyword count per day no matter how badly it ranks. Everything else about the free tier follows from that ceiling.
Paid plans look deeper because someone paying $10 reasonably expects to be told they've slipped to #43 rather than just "not in the top ten". The extra depth is most of what they're paying for.
The one optimisation that actually mattered
A SERP page for a keyword is the same page regardless of whose domain you're looking for. The position is computed locally afterwards. That means two users tracking "cheap rank tracker" in the same country do not need two API calls — they need one, and two local scans of the result.
So the cache key deliberately leaves the domain out:
key = (gl, hl, page, keyword)
It's a two-line decision with an unbounded payoff, because the savings scale with keyword overlap between customers. For a product where everyone in a niche tracks the same dozen commercial terms, that overlap is not a rounding error. It is the margin.
The TTL is six hours, which is a deliberately unromantic number. It has to be long enough to cover the daily scan plus any manual re-checks someone fires off in the same working day, and short enough that a position is never quoted from yesterday. Six hours does both. Setting it to zero restores exactly the old behaviour, which is how I can measure what it's worth.
One honest limitation: the cache is in-process, which is correct for a single worker and wrong the moment I run two.
Count it yourself, because the provider won't
My SERP provider has no balance endpoint. There is no API call that tells me how many credits I have left, which means the only trustworthy number is the one I record myself. Every costing action writes a row:
record_usage(session, "check", user_id=project.user_id,
keyword=kw.term, domain=project.domain,
credits=result.credits)
credits there is what the lookup actually spent — live pages
fetched, with cache hits counted as zero. Not an estimate, not the depth, the real
number.
Each row carries a ym field, a plain "2026-07" string.
Summing credits by that string gives a month's usage with no timezone reasoning at
all, which sounds like a small thing until you've debugged the alternative. The same
log serves three jobs that would otherwise be three systems: quota enforcement, abuse
detection, and knowing my cost per user.
The part that generalises
I went into this expecting to find out what a rank check costs. What I found is that there isn't a fixed figure to find — the cost is an output of three choices, each worth a multiple:
- How deep you look sets the worst case per keyword.
- What you deduplicate sets how much of your traffic you pay for twice.
- What your quota counts sets whether any of it stays bounded.
If you're building something thin on top of a metered API and the unit economics
look marginal, that's usually a design problem rather than a pricing one. Go and
find your equivalent of the break statement.
Written while building RankJot — Google rank tracking that emails you when your positions move. There's a free checker with no signup if you just want a one-off look.