Use cases / Google Search API
Google rank tracking
Who
Who runs this, and what for
| Team | What they decide with it |
|---|---|
| SEO teams | Which pages gained or lost positions after a release, and which keywords to work on next. |
| Agencies | What to report to each client every week, from one keyword set per client and country. |
| Content teams | Which queries show People also ask or video blocks worth writing for. |
| Retrieval and research tools | Which sources Google ranks for a question, as input to a pipeline rather than a report. |
Pipeline
The calls, step by step
01Fix the keyword set and the market
Rankings differ by country and language, so each keyword is tracked for one market. Pass country (where the search is made from) and language (Google's interface language) on every call and keep them fixed; Clair echoes them in query so each stored page records what produced it. Google also personalizes by city, device, and history, which this API does not vary: treat results as a neutral desktop view of that country.
GET /v1/search?engine=google_search&q=albert+einstein&country=us&page=1 · query
{ "q": "albert einstein", "language": "en", "country": "us", "page": 1 }Recorded response. Live values differ. 02Read pages until you find your domain
Each call returns one page of about ten organic results, with ads removed and Google's redirect links resolved to the destination URL. position counts across pages, so page 2 starts at 11. Read page 1, look for your domain, and fetch page 2 or 3 only when it is not there. Most trackers stop at the top 30; page 10 (positions 91 to 100) is the deepest Google serves reliably.
GET /v1/search?engine=google_search&q=albert+einstein&country=us&page=1 · results.0
{ "position": 1, "title": "Albert Einstein", "url": "https://en.wikipedia.org/wiki/Albert_Einstein", "source": "Wikipedia", "displayed_url": "https://en.wikipedia.org › wiki › Albert_Einstein", "date": null, "snippet": "Albert Einstein (14 March 1879 – 18 April 1955) was a German-born theoretical physicist ...", "sitelinks": [] }Recorded response. Live values differ. 03Record the SERP around the results
A position means less when a knowledge panel, a local pack, or People also ask sits above it. The same response carries those blocks as knowledge_graph, people_also_ask, local_results, videos, and related_searches, null or empty when Google did not show them. Storing which ones appeared tells you why clicks moved when positions did not.
GET /v1/search?engine=google_search&q=albert+einstein&country=us&page=1 · knowledge_graph
{ "title": "Albert Einstein", "type": "Theoretical physicist", "description": "Albert Einstein was a German-born theoretical physicist best known for developing the theory of relativity.", "description_source": { "title": "Wikipedia", "url": "https://en.wikipedia.org/wiki/Albert_Einstein" }, "facts": { "Born": "March 14, 1879, Ulm, Germany", "Height": "5′ 9″" } }Recorded response. Live values differ.
Code
A script to start from
rank_check.py
# Daily Google rank check: where does your domain rank for each keyword?
# Reads up to MAX_PAGES result pages per keyword and stops at the first hit.
import csv, datetime, os, requests
from urllib.parse import urlparse
API = "https://api.clair.im"
HEADERS = {"Authorization": f"Bearer {os.environ['CLAIR_API_KEY']}"}
DOMAIN = "example.com"
KEYWORDS = ["payment api", "accept payments online", "invoice software"]
COUNTRY, LANGUAGE, MAX_PAGES = "us", "en", 3 # each page is one request
FEATURES = ("knowledge_graph", "people_also_ask", "local_results", "videos")
def serp(q, page):
r = requests.get(f"{API}/v1/search", headers=HEADERS, timeout=60, params={
"engine": "google_search", "q": q, "country": COUNTRY,
"language": LANGUAGE, "page": page})
r.raise_for_status()
return r.json()
def ours(url):
host = (urlparse(url).hostname or "").removeprefix("www.")
return host == DOMAIN or host.endswith("." + DOMAIN)
today = datetime.date.today().isoformat()
with open("ranks.csv", "a", newline="") as f:
out = csv.writer(f)
for q in KEYWORDS:
rank, url, features = None, None, []
for page in range(1, MAX_PAGES + 1):
body = serp(q, page)
if page == 1: # SERP blocks sit on the first page
features = [k for k in FEATURES if body.get(k)]
hit = next((r for r in body["results"] if ours(r["url"])), None)
if hit:
rank, url = hit["position"], hit["url"]
break
if len(body["results"]) < 10:
break # Google has no further pages for this query
out.writerow([today, q, COUNTRY, rank, url, "|".join(features)])
Set CLAIR_API_KEY to a key subscribed to the Google Search API. Each call is one request against that API's monthly quota.
Cost
What it costs per month
| Schedule | Requests | Plan | Per month | Per 1,000 |
|---|---|---|---|---|
| 100 keywords, daily, top 10100 keywords × 1 page × 30 days. | 3,000 | Pay as you go | $15.90 | $5.30 |
| 500 keywords, daily, top 30500 × 3 pages × 30 days, at most. The script stops once it finds the domain, so the real count is lower. | 45,000 | Ultra | $149 | $3.31 |
| 2,000 keywords, weekly, top 202,000 × 2 pages × 4 weekly runs. | 16,000 | Pro | $56.29 | $3.52 |
| 5,000 keywords, daily, top 105,000 × 1 page × 30 days. | 150,000 | Mega | $399 | $2.66 |
Measured
Response times
| Google Search API | |
|---|---|
| Succeeded | 18 of 20 |
| Median response time | 6.2 s |
| 95th percentile | 25.2 s |
| Had knowledge graph | 28% |
| Had people also ask | 94% |
| Had related searches | 78% |
| Had local results | 11% |
| Had videos | 33% |
Limits
What to plan for
- Location is set per country. There is no city, ZIP, or coordinate targeting, so local rank tracking for one city is out of reach; use the Google Maps API for local business rankings instead.
- Results are the desktop page. Mobile rankings, which differ on many queries, are not available.
- Ads, AI Overviews, and shopping carousels are not returned. If you track paid competitors or AI Overview citations, you need a different API.
- Every call is a live fetch: there is no cache, and a repeated query is billed again. Deduplicate keywords before a run.
- Rankings move during the day. Run each market at the same hour so day-to-day changes reflect Google, not your schedule.
- Pages go up to 10. Google returns fewer results on some queries; a page with fewer than ten organic hits is usually the last.
FAQ
Common questions
Does position include ads or the local pack?
No. position counts organic results only, in the order Google printed them, across pages. Blocks such as the local pack are returned separately.
How accurate is it compared with what I see in my browser?
Your browser applies your location, signed-in history, and device. Clair's request has none of those, so it is closer to what a new visitor in that country sees on desktop. Small differences against a personal search are expected.
Can I track several countries?
Yes. Each keyword and country pair is its own series, and each page is one request. The cost table counts one market; multiply by the number of countries.
Can I search a single site?
Yes. q accepts Google operators, so site:example.com pricing returns that site's pages for the word pricing.
Run it on your own products
200 requests a month free on each API, no card. Enough to run the pipeline on a short list before choosing a plan.