cd /news/developer-tools/google-search-pagination-is-not-resu… · home topics developer-tools article
[ARTICLE · art-103979] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Google Search Pagination Is Not results.length

Reserp, a search API provider, clarified that Google Search pagination is based on organic-result offsets, not the number of visible result blocks returned. The company's API separates the 'start' parameter, which controls Google's organic offset, from the 'results' array, which contains visible blocks, and returns a 'nextStart' value for pagination. Reserp's documentation demonstrates a client pattern that keeps these concepts distinct, avoiding common mistakes like using 'results.length' as the next page offset.

read3 min views4 publishedAug 20, 2026

Disclosure: I work on Reserp. This article explains the public API contract and keeps application policy explicit.

Suppose a search API returns 17 URLs on its first response. What should the next page offset be?

With Google Search, the answer is not 17.

A visible search page can contain organic listings, news blocks, sitelinks, carousels, discussion replies, videos, and nested results. An API that preserves those visible blocks may return many URLs without changing Google's organic-result pagination rule.

Reserp makes that distinction explicit. Its start

parameter is Google's organic-result offset, while the results

array represents visible result blocks. This tutorial shows a direct client pattern that keeps those two concepts separate.

Reserp accepts a complete Google Search URL in one JSON field:

curl -X POST https://api.reserp.ai/v1/serp \
  -H "Authorization: Bearer $RESERP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.google.com/search?q=photonic+computing&gl=us&hl=en"}'

The URL can use normal Google parameters such as:

Parameter Meaning Example
q
search query photonic computing
gl
country us
hl
language en
start
organic-result offset 10
tbs
time or result filter qdr:w
tbm
search type nws

The Google num

parameter is unsupported and should not be sent.

A successful response contains pagination independent of results.length

:

{
  "pagination": {
    "start": 0,
    "nextStart": 10,
    "nextUrl": "https://www.google.com/search?q=photonic+computing&gl=us&hl=en&start=10"
  }
}

The rules are simple:

start

or use 0

for the first page;10

for the second page;20

for the third page;An invalid offset, such as start=17

, produces a non-billable 400 invalid_request

response.

The transport function below makes exactly one request. It does not retry or fetch another page:

import json
import os
from urllib.request import Request, urlopen

def search_once(google_url: str) -> dict:
    request = Request(
        "https://api.reserp.ai/v1/serp",
        data=json.dumps({"url": google_url}).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {os.environ['RESERP_API_KEY']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )

    with urlopen(request) as response:
        return json.load(response)

Call it for the first page:

first = search_once(
    "https://www.google.com/search"
    "?q=photonic+computing"
    "&gl=us"
    "&hl=en"
)

print(first["pagination"])

If—and only if—the application decides it needs another page, pass the returned URL into another explicit invocation:

second_url = first["pagination"]["nextUrl"]
second = search_once(second_url)

The important part is what the code does not say:

next_start = len(first["results"])

It also avoids burying a loop inside search_once

. The calling job may have its own page limit, request budget, cancellation signal, or queue. Those are application policies, not transport behavior.

Each result block may include text

, url

, and children

, but not every block has every field. In particular, text

is optional in the public contract:

for block in first.get("results", []):
    text = block.get("text")
    url = block.get("url")

    if text is not None:
        print(text)
    if url is not None:
        print(url)

Do not flatten children

unless your application has a reason to discard their URL boundaries. Reserp retains nesting precisely when flattening would lose descendant content or structure.

Every error body has four public fields:

{
  "ok": false,
  "error": "service_unavailable",
  "retryable": true,
  "billed": false
}

retryable

is authoritative for retry eligibility. billed

reports whether billing settled; it does not decide whether an error is retryable.

The one-request function above does not add a retry loop. A real application can inspect the HTTP error body and let its existing worker, queue, or scheduler decide what to do. That avoids a hidden retry colliding with a higher-level retry and turning one logical job into duplicate API calls.

Keep the three layers separate:

Google organic offset: start = 0, 10, 20, ...
Visible page content:   results = heterogeneous blocks
Application policy:     whether and when to request another page

Once those layers are separate, pagination is predictable. Follow pagination.nextUrl

, never infer an offset from the number of returned blocks, and keep each API invocation explicit.

The complete contract, including supported Google parameters and stable error codes, is available in the Reserp API documentation and OpenAPI JSON.

── more in #developer-tools 4 stories · sorted by recency
── more on @reserp 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/google-search-pagina…] indexed:0 read:3min 2026-08-20 ·