Rank tracking API: check Google positions with code, and what it costs
Find your domain's Google position for any keyword by country and device with a rank tracking API. Working Python, edge cases and cost per 1,000 keywords.
CrawlerData teamPublished 7 min read
Contents
A rank tracking API answers one question: where does this domain appear in Google for this keyword, in this country, on this device? Most pages that rank for “rank tracking API” describe a finished product with a dashboard. If you are building your own tracker, or adding rankings to a tool you already have, what you need is the function underneath, and a way to know what it will cost.
This guide builds it. We use the Google Search API to fetch the results, then write a find_rank() function with the edge cases that give wrong answers if you skip them. All results below come from requests we ran in October 2026. Rankings change constantly, so yours will differ.
How rank checking works
There is no “get my ranking” endpoint on Google. A rank checker does three things:
- Requests the search results for a keyword, country and device.
- Walks the organic results in order and counts them.
- Stops at the first result on your domain and returns that count, or tells you the domain was not found.
The first two steps are what a SERP API gives you. For a fuller picture of what the response contains, see What is a SERP API?. The third step is yours, and it is where the mistakes happen.
The function
Put your key in an environment variable first:
export CRAWLERDATA_API_KEY="YOUR_API_KEY"
import os
import time
from urllib.parse import urlparse
import requests
API_URL = "https://api.crawlerdata.com/v1/jobs"
HEADERS = {"Authorization": f"Bearer {os.environ['CRAWLERDATA_API_KEY']}"}
def google_search(query, country="us", device="desktop", page=1, location=None):
params = {"query": query, "country": country, "device": device, "page": page}
if location:
params["location"] = location # e.g. "Austin,Texas,United States"
body = {"endpoint": "google-search", "params": params, "queue": "live"}
resp = requests.post(API_URL, json=body, headers=HEADERS, timeout=180)
resp.raise_for_status()
data = resp.json()
if data["job"]["status"] != "completed":
raise RuntimeError(data["job"]["error"])
return data["result"]
def host_matches(link, domain):
host = (urlparse(link).hostname or "").removeprefix("www.")
return host == domain or host.endswith("." + domain)
def find_rank(domain, keyword, country="us", device="desktop", location=None, max_pages=3):
"""Position of `domain` for `keyword`, counting organic results only.
Returns {"rank", "url", "pages_checked"}; rank is None if the domain is not
in the first `max_pages` pages. Positions restart on every page, so we keep
our own running count and skip URLs already seen on an earlier page.
"""
seen, rank = set(), 0
for page in range(1, max_pages + 1):
organic = google_search(keyword, country, device, page, location)["organic_results"]
if not organic:
return {"rank": None, "url": None, "pages_checked": page}
for r in organic:
if r["link"] in seen:
continue
seen.add(r["link"])
rank += 1
if host_matches(r["link"], domain):
return {"rank": rank, "url": r["link"], "pages_checked": page}
return {"rank": None, "url": None, "pages_checked": max_pages}
Run it on a few keywords for a public domain:
def check_keywords(domain, keywords, **kwargs):
rows = []
for kw in keywords:
rows.append({"keyword": kw, **find_rank(domain, kw, **kwargs)})
time.sleep(0.5) # be gentle on the live queue
return rows
keywords = ["python download", "how to read a csv file in python", "pandas dataframe tutorial"]
for row in check_keywords("pandas.pydata.org", keywords, max_pages=3):
print(row)
This is what came back for pandas.pydata.org:
{'keyword': 'python download', 'rank': None, 'url': None, 'pages_checked': 3}
{'keyword': 'how to read a csv file in python', 'rank': 5, 'url': 'https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html', 'pages_checked': 1}
{'keyword': 'pandas dataframe tutorial', 'rank': 1, 'url': 'https://pandas.pydata.org/docs/user_guide/10min.html', 'pages_checked': 1}
The three keywords cost 5 credits: 1 each for the two found on page 1, and 3 for the one we searched three pages for and did not find. Not finding a domain is a normal result, not an error, and it costs more than finding it.
The edge cases that give wrong ranks
Subdomains and www. docs.python.org and www.python.org both belong to python.org. The function strips www. and then accepts the exact host or any subdomain, so a tracker for python.org correctly matched docs.python.org in our test. Without that, a docs subdomain would look like a competitor.
position is not your rank past page 1. The position field restarts at 1 on every page, so page 2’s first result is position 1 again. The function keeps its own rank counter across pages. It also skips a URL it has already counted, because the same URL can appear on two pages.
Organic results are not always 10. Google fills part of the page with other features, so a page can have 6 to 9 organic results, as in the five searches in our SERP API guide. Counting results rather than assuming 10 per page avoids a rank that is wrong by a few places on page 2.
Device and country change the answer. A rank is only meaningful with its device and country. Store them with every check, and request each combination separately:
for device in ("desktop", "mobile"):
print(device, find_rank("python.org", "python tutorial", device=device))
print("de", find_rank("python.org", "python tutorial", country="de"))
For python tutorial, python.org was first on desktop, on mobile and in Germany, all via docs.python.org/3/tutorial/. Many keywords will differ more, which is the reason to track them separately.
City-level rank. For searches with local intent, pass location, a Google location name such as Austin,Texas,United States: find_rank("yelp.com", "pizza", location="Brooklyn,New York,United States") put Yelp at rank 4 for a Brooklyn pizza search. Store the location with the rank, like country and device. Valid names come from the public GET https://api.crawlerdata.com/public/locations?q=austin lookup, and local rank tracking covers business listings.
The first match is not always the page you care about. find_rank() returns the first result on the domain. If several of your URLs rank for the same keyword, or a different page than the one you wanted ranks, the returned url tells you which. Store it with the rank, and alert when it changes.
Store the history
A single rank is a snapshot. The value is the trend, so write every check to a table with the date, keyword, country, device, rank and the ranking URL:
import sqlite3
from datetime import date
db = sqlite3.connect("ranks.db")
db.execute(
"""CREATE TABLE IF NOT EXISTS ranks (
day TEXT, keyword TEXT, country TEXT, device TEXT, rank INTEGER, url TEXT,
PRIMARY KEY (day, keyword, country, device))"""
)
def record(domain, keyword, country="us", device="desktop"):
r = find_rank(domain, keyword, country=country, device=device)
db.execute(
"INSERT OR REPLACE INTO ranks VALUES (?, ?, ?, ?, ?, ?)",
(date.today().isoformat(), keyword, country, device, r["rank"], r["url"]),
)
db.commit()
return r
A NULL rank means “not in the first pages that day”, which is different from a rank of 0. Treat it as such when you chart it.
What it costs
Every request is 1 credit, on every queue and device. A check costs one credit per page you request, so cost depends on how deep you look. Starter is $1.00 per 1,000 requests and the largest pack is $0.30 per 1,000, see pricing.
| What you track | Checks a month | Credits (one page each) | At $1.00 per 1,000 | At $0.30 per 1,000 |
|---|---|---|---|---|
| 100 keywords, daily, desktop | 3,000 | 3,000 | $3.00 | $0.90 |
| 1,000 keywords, daily, desktop | 30,000 | 30,000 | $30.00 | $9.00 |
| 1,000 keywords, daily, desktop and mobile | 60,000 | 60,000 | $60.00 | $18.00 |
| 1,000 keywords, weekly, desktop | about 4,300 | about 4,300 | about $4.30 | about $1.29 |
Each extra page you search multiplies that. If you only need to know “top 10 or not”, max_pages=1 keeps every check at 1 credit. Failed requests are not charged.
Run it on a schedule
For a few hundred keywords, the live queue and a loop are enough. For thousands, submit jobs to the fast or regular queue and collect the results later instead of holding connections open. The batch code is in Get Google search results as JSON with Python, and it works unchanged here. See SEO rank tracking for how teams run this daily for clients.
Next steps
To look at a single search before building anything, our free AI Overview checker runs without an account and shows the AI Overview, its sources and the top results for the country and device you pick.
You now have a find_rank() function that handles subdomains, pagination, device and country, a table for the history and a cost model you can check against your own keyword list. The Google Search API page lists every parameter with a full example response.
Frequently asked questions
- What is a rank tracking API?
- An API that tells you where a page or domain ranks in Google for a keyword, in a given country and on a given device. In practice you request the search results through a SERP API and find your domain in the organic results. The position is your count of organic results up to your first one.
- How do I get a keyword's Google ranking with Python?
- Request google-search for the keyword with a country and device, loop over organic_results and return the position of the first result whose host matches your domain. If it is not on the page, request the next page. The full function is in this guide.
- Does the position in the response equal the rank?
- Only on page 1. position restarts at 1 on every page, and the same URL can appear on two pages. Keep your own running count across pages and skip URLs you have already counted.
- How many credits does a rank check use?
- 1 credit per page requested. A domain on page 1 costs 1 credit, and a domain not found within 3 pages costs 3. In our test, three keywords cost 5 credits in total.
- How much does it cost to track 1,000 keywords daily?
- If every check is one page, 1,000 keywords checked daily is 30,000 credits a month: $30 at the Starter rate of $1.00 per 1,000 requests, and $9 at the largest pack's $0.30 per 1,000. Add a device or country and multiply by that number.
- Can I track desktop and mobile rankings?
- Yes. Set device to desktop or mobile on each request. Both cost the same, and rankings can differ between them, so store the device with every result.
- Rank tracking API
- Rank tracker API
- SERP API
- SEO
- Python
- Tutorial