AI Development

    How to build a SERP API rank checker

    By Desearch Editorial Team7 min read

    A SERP API rank checker takes a keyword list and one domain, calls GET /web, and records the best rank that domain reached. The results are JSON and CSV, so a later run can diff them. The checker does not need a browser. It is a short Python CLI in the standard library.

    The results are what Desearch returned at that moment, not a copy of any engine's ranking.

    The worked example is Check Keyword Rank in Desearch Web Results. The source is the serp-api-rank-checker folder in the Desearch Python examples repo.

    The basics

    • Base URL: https://api.desearch.ai
    • Auth: Authorization: $DESEARCH_API_KEY (the raw key from Desearch Console)
    • Endpoint: GET /web
    • Parameters: query (required) and start (results to skip, default 0)
    • SDK: desearch-py 1.2.1, method web_search, if you want a client instead of plain HTTP

    The example CLI does not import the SDK. requirements.txt lists pytest only. The snippets below are either taken from that folder or are a desearch-py 1.2.1 call.

    Get a key

    Create a key in Desearch Console and export it:

    export DESEARCH_API_KEY=your_key
    

    The example reads DESEARCH_API_KEY. It can also read a file path from DESEARCH_API_KEY_FILE. The key is sent on the request and is not written into the output files.

    Make one call

    This asks for the first page of results for one keyword:

    curl --get 'https://api.desearch.ai/web' \
      --header "Authorization: $DESEARCH_API_KEY" \
      --data-urlencode 'query=web search api' \
      --data-urlencode 'start=0'
    

    The same call in desearch-py 1.2.1:

    import asyncio
    import os
    from desearch_py import Desearch
    
    
    async def main() -> None:
        async with Desearch(api_key=os.environ["DESEARCH_API_KEY"]) as client:
            results = await client.web_search(
                query="web search api",
                start=0,
            )
            print(results)
    
    
    asyncio.run(main())
    

    web_search takes query and start. start is how many results to skip. There is no num argument. include_metadata is keyword-only and defaults to false. query and start match GET /web.

    The example client builds the URL with the standard library:

            url = BASE + path + "?" + urllib.parse.urlencode(params)
            req = urllib.request.Request(url, headers={"Authorization": self.key, "User-Agent": UA})
    

    BASE is https://api.desearch.ai. The response body is a JSON object. data is an array of results, and each result has title, link, and snippet. The parameter list for this endpoint is in the Web Search API reference. The product page is Web Search.

    Run the checker

    Put one keyword per line in a text file. The sample file in the folder is samples/keywords.txt. Validate the keyword list first. The checker skips a blank line.

    python rank_check.py --keywords samples/keywords.txt --domain desearch.ai
    # a later run, diffed against the first one
    python rank_check.py --keywords samples/keywords.txt --domain desearch.ai --diff runs/<first-run>/summary.json
    

    The flags are --keywords, --domain, --pages (default 2), --step (default 20), --out (default runs), and --diff.

    For each keyword the checker requests one call per page. The start offset for a page is the page index times step. The default is two calls, start=0 and start=20. It drops duplicate URLs, numbers the remaining rows from 1, and records the first rank whose host is the domain or a subdomain of it.

    def check_keyword(c, kw, domain, pages, step=20):
        got = []
        for p in range(pages):
            start = p * step
            st, body = c.get("/web", {"query": kw, "start": start}, f"{re.sub(r'[^a-z0-9]+', '-', kw.lower()).strip('-')[:30] or 'blank'}-s{start}")
            got.append((start, core.items_of(body) if st < 300 else []))
        rows, stats = core.merge_pages(got)
        return {"keyword": kw, "rows": rows, "page_stats": stats, **core.keyword_summary(kw, domain, rows)}
    

    Page with start steps of 20. On 2026-10-16, a start of 10 added 0 to 6 new URLs per keyword, and 4 of 10 keywords added none on the first of those runs. A start of 20 shared 4 of 20 URLs with start 0.

    Read the output

    Each run writes runs/<timestamp>/. results.csv has one row per result: keyword, rank, title, url, domain, page_start. summary.json has the best rank per keyword. The label is the rank, or not in top N when the domain did not appear. N is how many unique URLs that keyword returned.

    A later run with --diff compares best ranks to the earlier summary.json. The change label is same, entered, dropped out, up N, or down N.

    On 2026-10-16 at 11:54 (Asia/Tbilisi) the checker ran 10 keywords for desearch.ai with --pages 2 --step 20, and diffed that run against one from 11:52. These are the best ranks from that run:

    KeywordBest rank
    serp apinot in top 37
    web search api12
    search api for ai agentsnot in top 39
    twitter search api11
    ai search engine apinot in top 40
    bittensor subnet 22not in top 38
    decentralized search engine16
    x posts search apinot in top 14
    json search results api4
    search api for llmnot in top 40

    The printed diff lines, with unchanged None to None lines trimmed, were:

    KeywordDiff
    decentralized search engine16 -> 16 (same)
    json search results api4 -> 4 (same)
    twitter search api11 -> 11 (same)
    web search api12 -> 12 (same)
    x posts search api4 -> None (dropped out)

    x posts search api dropped out because that query returned 14 unique results on this run, and the earlier match was not among them. Keywords with few results move more between runs, so check them twice before acting.

    The results are what Desearch returned at that moment, not a copy of any engine's ranking.

    Two runs about a minute apart the same morning (11:51:51 and 11:52:47, Asia/Tbilisi) shared 7 of 10 to 10 of 10 of each keyword's top 10 URLs. The top 10 order was the same for 4 of 10 keywords. The best rank was unchanged for 9 of 10 keywords. One keyword entered at rank 16.

    What it costs

    Web Search is listed at $1.00 per 1,000 searches on the pricing page (reference rate as of 7 May 2026). Console lists the same rate as $0.10 per 100 searches. Console shows the rate on your account.

    That published rate is $0.001 per search. On 2026-10-16 each call cost $0.001, so 10 keywords and 2 pages is 20 calls and $0.02 per run.

    Across 40 calls in those two runs, the median time was 1.92 seconds, the minimum 1.06 seconds, and the maximum 8.25 seconds.

    Run the example

    The example folder includes rank_check.py, core.py, offline tests, a sample keyword file, and a README.

    python3 -m venv .venv && . .venv/bin/activate
    pip install -r requirements.txt   # pytest only, the CLI itself uses the standard library
    

    Export DESEARCH_API_KEY, then run the checker:

    python rank_check.py --keywords samples/keywords.txt --domain desearch.ai
    

    The offline tests do not call the API. On 2026-10-16, python -m pytest -q reported 9 passed.

    The Desearch MCP server has a web-search tool with the same search. It takes query and an optional start. Ask an agent to search one keyword and say where the domain appears in the results it received.

    Frequently asked questions

    How do I page past the first results?

    Pass start in steps of 20, or leave the checker on its default --step 20. Two pages then use start=0 and start=20.

    How do I compare a later run?

    Pass --diff with the earlier summary.json. The change label is same, entered, dropped out, up N, or down N.

    What does the rank mean?

    It is the first row, after duplicate URLs are removed, whose host is the domain or a subdomain. The results are what Desearch returned at that moment, not a copy of any engine's ranking.

    Where is the full example?

    The CLI, the offline tests, and a sample run are in serp-api-rank-checker. A walkthrough of the same checker is on Check Keyword Rank in Desearch Web Results.

    Put your next idea to work.

    Try a query in the playground, then use the docs to bring Desearch into your application.

    Browse more articles