Skip to content
SpideyData

SERP guide · mini project

Build a Rank Tracker with a SERP API

Google: live · not GA

A small project you can follow end to end. The output is one SQLite table of daily positions; you own the schedule, the storage and any alerts.

Published · Updated

Architecture

  1. Your cron job (or any scheduler you run) starts once a day.
  2. For each keyword, POST /v1/serp/google/organic/tasks and keep the job id (202).
  3. Poll GET /v1/serp/tasks/{task_id} until status is completed or failed.
  4. Find your domain in job.result.organic[] and store the rank.

Schema for positions

SQLite table
CREATE TABLE IF NOT EXISTS positions (
  date    TEXT NOT NULL,   -- YYYY-MM-DD of the check
  keyword TEXT NOT NULL,
  country TEXT NOT NULL,   -- the country sent in the task body
  rank    INTEGER,         -- NULL when your domain is not in organic[]
  url     TEXT,            -- the matching result URL
  PRIMARY KEY (date, keyword, country)
);

Retry-aware polling

Read three things from the job before deciding what to do next.

Python: queue a task and poll it
import time, sqlite3, requests
H = {"Authorization": f"Bearer {KEY}"}
def track(kw, domain, country="US"):
    r = requests.post(f"{BASE}/v1/serp/google/organic/tasks", headers=H,
                      json={"q": kw, "country": country})
    if r.status_code != 202:          # 4xx/5xx: read the error body, do not poll
        raise RuntimeError(f"task not queued: {r.status_code} {r.text}")
    job = r.json()["job"]
    while job["status"] in ("queued", "running"):
        time.sleep(5)
        r = requests.get(f"{BASE}/v1/serp/tasks/{job['id']}", headers=H)
        r.raise_for_status()
        job = r.json()["job"]
    if job["status"] == "failed":
        return None, (job.get("error") or {}).get("retryable", False)
    hit = next((o for o in job["result"]["organic"] if domain in o["url"]), None)
    return (hit or {}).get("rank"), False
A failed job
{"job":{"id":"<task_id>","kind":"google_organic","status":"failed","attempt_count":3,"max_attempts":3,
        "error":{"code":"<code>","message":"<message>","retryable":false}}}
  • job.status: queued and running mean keep polling; completed and failed are final.
  • job.error.retryable: false by default; only queue a new task when it is true.
  • attempt_count / max_attempts: how many attempts the task used out of its limit (default 3).

Cost estimate and alerts

  • Daily cost = keywords × countries × 2 Credits (display rate). Searches are charged per successful search; failed searches are not charged, and polling is free.
  • Alerts are yours: compare today's rank with yesterday's in SQLite and notify however you like. The platform does not push alerts or schedule checks.

Launch status for this page

Check engine availability before choosing an endpoint. Google also supports News, Images and asynchronous tasks.

  • GoogleLiveGoogle is callable today with a Bearer API key.

SERP searches are charged per successful search at a display rate of 2 Credits per search; failed searches are not charged. Polling task status is free. The current rate is on Pricing.

Frequently asked questions

Does the platform schedule daily checks?

No. Your own cron job or scheduler starts the run; the API only takes tasks and answers polls.

How do I know a failure is retryable?

Read job.error.retryable. It defaults to false, so only queue a new task when it is true.

Is polling billed?

No. Polling task status is free; only a successful search is charged, at a display rate of 2 Credits.

Can I run Google tasks today?

Google is callable today with a Bearer API key. The SERP API as a whole is not GA.

Track your first keyword

Queue one Google organic task, poll it, and write the rank to your positions table.