Tutorial 10 min read

SERP Tracking API Workflow: Build a Rank Tracker in 2026

Build a SERP tracking API workflow with SearchCans and Python. Target country and language, store rank history, handle retries, and estimate credit use.

(Updated: ) 1,820 words

A SERP tracking API workflow sends a fixed keyword, country, and language to a search API on a schedule, stores the returned organic positions, and compares each run with prior snapshots. The reliable version is not just an API call. It also defines a query contract, normalizes target URLs, records missing rankings, retries only temporary failures, and tracks credit use.

Key takeaways

  • Use one stable keyword, country, language, and page-depth definition for every comparison.
  • Read ranking positions from data.organic[] instead of parsing result-page HTML.
  • Store the complete observed result set, not only your own domain’s current rank.
  • Retry lane and temporary service errors, but stop on authentication, permission, and credit errors.
  • Treat a missing result as data. Do not silently carry yesterday’s position forward.

What does a SERP tracking API workflow measure?

A SERP tracking API workflow measures where a URL or domain appears in a repeatable set of search results. Each observation needs five pieces of context:

Field Why it matters
Keyword Defines the search demand being observed
Search engine Keeps Google and Bing result sets separate
Country Selects the regional Google index
Language Sets the result-language preference
Checked time Makes movement and freshness measurable

SearchCans accepts country as an ISO 3166-1 alpha-2 code and language as a BCP 47 language code. Use the same pair on every scheduled run. A ranking from country: "us", language: "en" should not be compared with a run that used another market definition.

The current Google Search API request does not expose a device selector or city/ZIP parameter. Do not label the output as mobile, desktop, or city-level tracking unless the request contract actually supports that dimension. The country and language reference lists the available values.

What should the rank tracking data model contain?

Keep the query definition separate from the observations. That makes it possible to change a schedule without rewriting history and prevents one keyword’s settings from leaking into another.

A compact model can use three tables:

  1. tracked_queries: keyword, engine, country, language, target host, and page depth.
  2. serp_runs: query ID, checked time, request status, and request identifier when available.
  3. serp_results: run ID, position, URL, title, and snippet.

Store all returned organic results for the requested page depth. If you save only your target URL, you lose the evidence needed to explain why a position changed. A competitor, marketplace, documentation page, or search feature may have entered the result set even when your page did not change.

Use NULL for a target that is not found within the observed depth. A missing rank is different from position zero, and it is also different from a failed API request.

How do you request Google rankings from SearchCans?

The endpoint is POST https://www.searchcans.com/api/v1/search. The required fields are t for the engine and s for the query. This request fetches the first Google result page for a US English observation:

import os
import requests

SEARCH_URL = "https://www.searchcans.com/api/v1/search"
API_KEY = os.environ["SEARCHCANS_API_KEY"]

response = requests.post(
    SEARCH_URL,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "t": "google",
        "s": "google search api",
        "country": "us",
        "language": "en",
        "p": 1,
        "d": 30000,
    },
    timeout=35,
)
response.raise_for_status()

body = response.json()
if body.get("code") != 0:
    raise RuntimeError(f"SearchCans error {body.get('code')}: {body.get('msg')}")

organic = body.get("data", {}).get("organic", [])
for result in organic:
    print(result.get("position"), result.get("title"), result.get("link"))

The response’s organic[] items include the observed position, title, link, snippet, displayed link, and source fields. Use the structured position. Raw HTML is useful for a separate audit, but it should not be the primary parser for a routine rank tracker.

How do p and page change the observation?

Use p when you need one specific result page. Use page when you want pages 1 through N in one request. Do not set both above 1; p takes priority if they conflict.

{
  "t": "google",
  "s": "serp tracking api",
  "country": "us",
  "language": "en",
  "page": 3
}

The example above requests pages 1, 2, and 3 and costs three credits. A specific p: 3 request returns only page 3 and costs one credit. Fix the depth in the query definition so a movement from position 9 to position 14 is not confused with a change from one-page tracking to three-page tracking.

How do you match a target domain without false positives?

Do not use substring matching such as "example.com" in url. It can match unrelated hosts, query strings, or paths. Parse the hostname and normalize a leading www. instead.

from urllib.parse import urlparse


def normalize_host(url_or_host: str) -> str:
    value = url_or_host.strip().lower()
    parsed = urlparse(value if "://" in value else f"https://{value}")
    host = parsed.hostname or ""
    return host[4:] if host.startswith("www.") else host


def find_domain_rank(organic: list[dict], target_host: str):
    expected = normalize_host(target_host)
    for result in organic:
        result_url = result.get("link") or result.get("url") or ""
        if normalize_host(result_url) == expected:
            return {
                "position": result.get("position"),
                "url": result_url,
                "title": result.get("title") or "",
            }
    return None


match = find_domain_rank(organic, "www.searchcans.com")
print(match or {"position": None, "reason": "not found in observed depth"})

For page-level tracking, normalize the path separately and decide in advance whether query strings, fragments, trailing slashes, and HTTP-to-HTTPS redirects count as the same URL. Keep both the raw observed URL and the normalized comparison key in storage.

How do you store rank history in SQLite?

SQLite is enough for a small tracker and keeps the example auditable. Use a unique key that prevents the same query and timestamp from being inserted twice.

import json
import sqlite3
from datetime import datetime, timezone


def save_run(database_path: str, query_key: str, results: list[dict]) -> None:
    checked_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
    with sqlite3.connect(database_path) as connection:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS serp_runs (
                query_key TEXT NOT NULL,
                checked_at TEXT NOT NULL,
                result_json TEXT NOT NULL,
                PRIMARY KEY (query_key, checked_at)
            )
            """
        )
        connection.execute(
            "INSERT INTO serp_runs(query_key, checked_at, result_json) VALUES (?, ?, ?)",
            (query_key, checked_at, json.dumps(results, ensure_ascii=False)),
        )


save_run("rank_history.sqlite3", "google|google search api|us|en|p1", organic)

For a production system, split each result into rows and add indexes for keyword, checked time, target host, and position. Keep the raw JSON as an audit artifact, but do not make dashboards repeatedly parse a large blob.

Which failures should a rank tracker retry?

Retry only failures that can change when you wait. SearchCans documents two lane-related cases:

  • HTTP 429: the request was rejected before reaching the API logic.
  • API code 1010 inside an HTTP 200 response: all Parallel Lanes were occupied.

Both are temporary. Queue the work, cap concurrency to the account’s available lanes, and retry with a short backoff. HTTP 500 and 503, network timeouts, and API timeout code 1001 are also retry candidates.

Do not repeatedly retry HTTP 400, 401, 402, or 403. Those indicate an invalid request, bad credentials, insufficient credits, or missing permission. Retrying without changing the cause only delays the job and makes logs harder to read. The error code reference lists the current meanings.

import random
import time

RETRYABLE_HTTP = {429, 500, 503}
RETRYABLE_API_CODES = {1001, 1002, 1003, 1004, 1005, 1006, 1009, 1010}


def backoff(attempt: int) -> None:
    delay = min(8.0, 0.5 * (2 ** attempt))
    time.sleep(delay + random.uniform(0, delay * 0.2))

Set application concurrency to the lane count rather than launching an unbounded thread pool. Parallel Lanes limit simultaneous live requests, not the number of requests allowed in an hour. See Rate Limits and Parallel Lanes before setting worker counts.

How often should rankings be checked?

Match the schedule to the decision the data supports:

  • Daily: active launches, incident monitoring, or a small set of commercial keywords.
  • Two or three times per week: established pages where daily movement would not change a decision.
  • Weekly: broad portfolios, long-tail queries, and discovery work.

More frequent checks do not automatically create better SEO decisions. They create more observations, more storage, and more noise. Search results can move between runs because of index updates, regional variation, result features, and normal volatility. Use rolling medians or several consecutive observations before treating a one-position move as a trend.

Run each market at a consistent UTC time, record delayed or failed runs, and never replace a failed observation with the previous rank.

How do you estimate SERP tracking API cost?

Start with the number of tracked queries, markets, checks, and requested pages:

monthly credits = keywords x markets x checks per month x pages per check

Tracking 500 keywords in two markets once per day at one page per check uses about 30,000 credits in a 30-day month. Requesting pages 1 through 3 would use about 90,000 credits. This estimate covers search requests only; your database, scheduler, alerts, and reporting have their own operating costs.

SearchCans uses prepaid credits, and the current plan, lane count, and credit validity are listed on the pricing page. Build estimates from that live source instead of copying a price into application logic.

What should a production rank tracking dashboard show?

A useful dashboard should answer a small number of operational questions:

  • Is the target present within the observed depth?
  • What is the latest position and seven-run median?
  • Which URL ranks for the query now?
  • Did the ranking URL change?
  • Did a run fail, return no results, or complete normally?
  • How many credits and retries did the collection job use?

Add alerts for missing runs, repeated authentication or credit errors, ranking URL changes, and sustained position changes. Avoid alerting on every single-position move. A noisy alerting system gets ignored quickly.

FAQ

Is a SERP API the same as a rank tracking API?

No. A SERP API returns the observed search results. A rank tracking system adds scheduling, target matching, historical storage, change detection, and reporting around those results.

Can this workflow track local or mobile rankings?

It can track the country and language supported by the current SearchCans Google Search API. The current request contract does not expose city/ZIP or device parameters, so the workflow should not claim those dimensions.

Should every keyword be checked every day?

No. Check often enough to support a decision. Daily checks suit a focused launch or commercial set; larger long-tail portfolios usually need a slower schedule.

What happens when the target URL is not found?

Store a completed observation with a null target position and the requested depth. Do not write position zero and do not reuse the last known rank.

Where can I test the request before scheduling it?

Use the SearchCans Playground to inspect a request and response, then move the same payload into your script. Keep the API key in an environment variable and review the Google Search API reference before production use.

A rank tracker becomes trustworthy when each row can be traced back to a fixed query definition and a real observed result set. Start with a handful of business-critical queries, verify the stored output, then expand the schedule and concurrency only after the collection pipeline is stable.

Tags:

Tutorial SERP API SEO Python API Development
SearchCans Team

SearchCans Team

SERP API & Reader API Experts

The SearchCans engineering team builds high-performance search APIs serving developers worldwide. We share practical tutorials, best practices, and insights on SERP data, web scraping, RAG pipelines, and AI integration.

Ready to build with SearchCans?

Test SERP API and Reader API with 100 free credits. No credit card required.