Rank Tracking API
Rank tracking is a data pipeline: the same keywords every day, clean positions out the other end. One call returns the full result page as JSON: organic listings, ads, People Also Ask, local pack, knowledge graph and the AI Overview.
Who it's for
Rank-tracking platforms adding SERP data at volume, SEO teams building their own trackers, and data teams feeding positions into a warehouse.
What you get
- Organic positions with titles, URLs and snippets, page 1 through 10
- SERP features in the same response: PAA, related searches, knowledge graph, local pack, ads
- The AI Overview when Google shows one, and null when it does not, so presence is a metric too
- Country on every call, with city-level targeting coming, so one pipeline covers every market
Try it
curl -X POST "https://api.answerline.dev/v1/monitor/google" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"project management software","country":"US","include":{"aioverview":{"markdown":true}}}'
Engines for this workload
What a rank tracker measures
A rank tracker runs the same keyword list on a schedule and turns the result pages into time series. Most disagreements about ranking data come from undefined metrics, so define them first.
| Metric | Definition |
|---|---|
| Organic rank | 1-based index of the first organic result on a tracked domain, after sorting organicResults by page then position. Null when absent within the requested depth. |
| Best URL | The link of that result. A change of best URL with a stable rank is still worth logging. |
| Cannibalization count | Number of organic results on the tracked domain for one keyword. |
| Top-3 / top-10 share | Keywords with rank ≤ 3 (or ≤ 10) ÷ keywords tracked. |
| Average rank | Mean rank over keywords that ranked. Report the ranked count beside it: keywords dropping out shrink the denominator and can make the average improve. |
| Visibility score | Σ over keywords of weight(rank), with weight(rank) a click-through curve you choose, divided by Σ of weight(1). Ranges from 0 to 1. |
| Rank change | rank(today) − rank(previous run), for keywords ranked in both runs. Entering or leaving the tracked depth is reported separately. |
| Volatility | Mean absolute rank change across keywords ranked in both runs, per keyword group. |
| Feature presence | Share of keywords whose result page contained a given feature: AI Overview, local pack, People Also Ask, shopping cards, ads, knowledge graph. |
| AI Overview citation | Whether a tracked domain appears in aioverview.sources for the keyword. |
Visibility score holds up best in front of stakeholders because it weights a move from 4 to 2 more than a move from 48 to 46. If you have Search Console click-through data per position, use it for the weights instead of a published curve.
Architecture
keywords ──> daily scheduler ──> POST /v1/async/task/batch (≤500 per call)
│
rank/feature views <── serp_snapshots <── webhook receiver
▲
└── reconciler (GET /v1/async/task/{id})
The submit, receive and reconcile loop is the one in the AI visibility tracking guide; this page covers what is specific to ranks.
Keyword set design
- One row per keyword × market.
country(orgl) is required;location(e.g.Austin,Texas,United States) oruuletargets a city. - Tag keywords by product line, funnel stage and whether they are branded. Every report slices by these tags, and adding them later means rebuilding history.
- Choose depth per group: money keywords at
pages: 1daily, long-tail discovery keywords atpages: 3weekly.pagesaccepts 1 to 10. - Freeze the keyword text. Normalize whitespace and case on insert and never edit in place; a corrected spelling is a new keyword.
Schedule and throughput
Run daily, at a consistent hour so time-of-day effects stay constant. The idempotency key encodes the slot:
rank:{keyword_id}:{run_date}
The market lives on the keyword row, so keyword_id identifies it. If one keyword row covers several markets, add those to the key.
There is no concurrency cap; the queue holds about 100,000 tasks of a typical prompt at a time (see rate limits). A task that has not started within 4 hours of creation fails with QUEUE_WAIT_EXCEEDED and is not charged, so check that a day’s keyword list drains well within that window. Batch and webhook mechanics are in async tasks and webhooks.
Storage schema
Store one snapshot per task and derive narrow tables from it. The snapshot lets you recompute ranks when you add a domain or fix a matching rule.
create table keywords (
keyword_id bigint primary key,
keyword text not null,
country text not null,
pages int not null default 1,
tags text[] not null default '{}'
);
create table serp_snapshots (
task_id uuid primary key,
keyword_id bigint not null references keywords,
run_date date not null,
status text not null,
credits numeric,
result jsonb, -- response.result as delivered
unique (keyword_id, run_date)
);
create table organic (
task_id uuid references serp_snapshots,
rank int not null, -- 1-based after sorting by page, position
page int not null,
position int not null,
domain text not null,
link text not null,
title text,
primary key (task_id, rank)
);
create table features (
task_id uuid primary key references serp_snapshots,
aio boolean not null,
aio_cited_domains text[] not null default '{}',
local_pack boolean not null,
paa int not null,
ads_top int not null,
shopping boolean not null,
knowledge_graph boolean not null
);
unique (keyword_id, run_date) catches a second task for the same slot even if something bypasses idempotency keys.
Change detection
Compare days once every task in the run has resolved:
- Rank moves beyond a per-group threshold, for keywords ranked on both days.
- Keywords entering or dropping out of the tracked depth.
- Best URL changes, which frequently precede a rank change.
- Feature flips: an AI Overview or local pack appeared or disappeared, or a tracked domain gained or lost an AI Overview citation.
Google result pages fluctuate day to day. Alert on moves that persist for two runs, or that cross a boundary that matters, such as leaving the top 3 or page 1. SERP features change tracking covers feature diffs.
End-to-end Python
Build the day’s tasks
import datetime as dt
def payload(kw):
return {"query": kw["keyword"], "country": kw["country"],
"pages": kw["pages"], "include": {"aioverview": {"markdown": False}}}
def day_tasks(db, run_date=None):
run_date = run_date or dt.date.today().isoformat()
cols = ["keyword_id", "keyword", "country", "pages"]
for row in db.execute(f"select {', '.join(cols)} from keywords").fetchall():
kw = dict(zip(cols, row))
yield {
"taskType": "GOOGLE",
"payload": payload(kw),
"idempotencyKey": f"rank:{kw['keyword_id']}:{run_date}",
"webhook": {"url": "https://hooks.example.com/serp"},
}
Submit in chunks of 500 and record each accepted task.id in serp_snapshots with status QUEUED, skipping items refused with RESOURCE_ALREADY_EXISTS.
Parse a delivery
After the receiver has verified the signature and dropped test deliveries, it calls apply. The reconciler calls the same function with GET /v1/async/task/{id} bodies, which have the same shape.
from urllib.parse import urlsplit
from psycopg.types.json import Jsonb
def host(url):
return (urlsplit(url).hostname or "").removeprefix("www.")
def apply(db, d):
task = d["task"]
result = d["response"].get("result") if task["status"] == "COMPLETED" else None
with db.transaction():
cur = db.execute(
"update serp_snapshots set status = %s, credits = %s, result = %s "
"where task_id = %s and result is null",
(task["status"], d["credits"]["creditsCharged"], Jsonb(result) if result else None, task["id"]))
if cur.rowcount and result:
explode(db, task["id"], result)
def explode(db, task_id, r):
organic = sorted(r.get("organicResults", []), key=lambda o: (o.get("page", 1), o.get("position", 0)))
for rank, o in enumerate(organic, start=1):
db.execute("insert into organic values (%s,%s,%s,%s,%s,%s,%s) on conflict do nothing",
(task_id, rank, o.get("page", 1), o.get("position", rank), host(o["link"]), o["link"], o.get("title")))
aio = r.get("aioverview")
db.execute("insert into features values (%s,%s,%s,%s,%s,%s,%s,%s) on conflict do nothing", (
task_id,
bool(aio),
sorted({host(s["url"]) for s in (aio or {}).get("sources", [])}),
"localResults" in r,
len(r.get("peopleAlsoAsk", [])),
sum(1 for a in r.get("ads", []) if a.get("blockPosition") == "top"),
bool(r.get("shoppingCards")),
"knowledgeGraph" in r,
))
where result is null makes a repeated delivery of a completed task a no-op; a repeated FAILED delivery rewrites the same status.
Compute rank, visibility and volatility
-- Rank per keyword per day for one domain
create view domain_rank as
select s.keyword_id, s.run_date, min(o.rank) as rank, count(o.rank) as urls_ranking
from serp_snapshots s
left join organic o on o.task_id = s.task_id and o.domain = 'acme.com'
where s.status = 'COMPLETED'
group by s.keyword_id, s.run_date;
-- Visibility score with a click curve you maintain in a table ctr(rank int, weight numeric)
select r.run_date,
sum(coalesce(c.weight, 0)) / (count(*) * (select weight from ctr where rank = 1)) as visibility
from domain_rank r left join ctr c on c.rank = r.rank
group by r.run_date order by r.run_date;
-- Volatility: mean absolute change for keywords ranked on both days
select today.run_date,
avg(abs(today.rank - prev.rank)) as volatility,
count(*) as keywords_compared
from domain_rank today
join domain_rank prev on prev.keyword_id = today.keyword_id
and prev.run_date = today.run_date - 1
where today.rank is not null and prev.rank is not null
group by today.run_date;
Cost planning
At the Starter tier, a Google Search task costs $0.00103 for one page, $0.00071 more for each extra page, and $0.00071 more when include.aioverview is set and Google shows an overview; include.paaAioverview is free. A task reserves its maximum cost when accepted and is charged for what the result contains, including the pages returned. Failed tasks are not charged. Higher tiers, reached by 30-day spend, cost less. Details are in balance and pricing.
| Request | Cost per task |
|---|---|
| 1 page | $0.00103 |
| 1 page + AI Overview | $0.00174 |
| 2 pages + AI Overview | $0.00245 |
| 3 pages | $0.00245 |
| 10 pages | $0.00742 |
Worked example: an agency tracks 1,500 keywords daily in the US, and 300 of them in the UK too.
- 1,800 tasks per day at 1 page + AI Overview: 1,800 × $0.00174 = $3.13
- A 30-day month: $93.96
- Moving the 1,500 US keywords to 2 pages: +1,500 × $0.00071 × 30 = +$31.95
- A weekly 10-page crawl of 100 discovery keywords: 100 × $0.00742 × 4 = $2.97 a month
Higher tiers, reached by 30-day spend, cost less; tiers are on pricing. A new account’s 100 free requests work on Google Search too: 100 keyword checks, with or without the AI Overview.
Pitfalls
Rank from position alone. Sort by page then position and number the list yourself, so ranks stay correct whether or not positions continue across pages.
Treating “not found” as rank 100. A sentinel number corrupts averages. Store null, report “not ranked” counts, and average over ranked keywords only.
Matching on the display URL. displayedLink is formatted for humans. Derive the domain from link, and decide whether subdomains count as the same site.
Redirect links. Some results carry redirectLink beside link. link is the destination; use it.
Assuming features are empty arrays. localResults, knowledgeGraph, peopleAreSaying and shoppingCards are omitted when the page did not have them, and aioverview is null when no AI Overview appeared. Test for presence, not length.
Comparing features across markets. Result pages in different countries differ in which features they show. Keep feature-presence series per market.
Mixing url and targeting fields. You can pass a full Google search URL as url instead of query, but not together with pages. Use one shape per keyword and keep it stable.
Unfinished runs in reports. Compute daily metrics after every task has resolved, or report the completed count alongside.
Which engines fit
Google Search is the engine for classic rank tracking: organic results, SERP features and the AI Overview in one response. Two neighbors are worth adding once positions are stable:
- AI Mode for the conversational answer on the same keyword, with city-level
locationtargeting, at $0.00206 per task. - ChatGPT, Gemini, Perplexity, Copilot and Grok for category keywords phrased as questions, where rank becomes citation position; see AI visibility tracking.
Rank tracking pipelines with batches goes further on batching, and the quickstart sends the first request.
Related: building a Google rank tracker and enterprise rank tracking.
Questions
How is rank calculated from the response?
Each item in result.organicResults carries position and page. Sort by page, then position, and the 1-based index of the first result whose domain matches yours is your organic rank. If no result matches within the pages you requested, record the keyword as not ranked rather than as a number.
How many pages should I request per keyword?
As deep as your decisions need. One page covers top-10 tracking at the base price; each extra page adds $0.00071, and pages goes up to 10.
Does the response include SERP features?
Yes. The same result can include peopleAlsoAsk, relatedSearches, localResults, ads, shoppingCards, knowledgeGraph, peopleAreSaying and, when include.aioverview is set, the AI Overview. Features that did not appear on the page are omitted or, for the AI Overview, null.
Can I track mobile and desktop separately?
Yes: set device to desktop, mobile, ios or android. Treat each device as its own series, because the result pages differ.
How do I avoid double-counting when a webhook is delivered twice?
Use the task id as the primary key of your snapshot table and make the write a no-op when a result is already stored. Deliveries can repeat and arrive out of order, and task.id is covered by the webhook signature.
More use cases
AI Visibility API: brand mentions and citations in AI
The GEO measurement layer: how ChatGPT, Gemini and Copilot describe a brand, with mentions, citations and the wording users see.
Local Rank Tracking API
AI answers and Google results per market as parsed JSON: city-level on AI Mode today, US-state targeting coming soon.
Competitor Analysis API
See which competitors ChatGPT, Gemini and Copilot recommend and cite for any prompt set, as JSON.