{"slug": "google-search-pagination-is-not-results-length", "title": "Google Search Pagination Is Not results.length", "summary": "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.", "body_md": "*Disclosure: I work on Reserp. This article explains the public API contract and keeps application policy explicit.*\n\nSuppose a search API returns 17 URLs on its first response. What should the next page offset be?\n\nWith Google Search, the answer is not 17.\n\nA 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.\n\n[Reserp](https://reserp.ai/) makes that distinction explicit. Its `start`\n\nparameter is Google's organic-result offset, while the `results`\n\narray represents visible result blocks. This tutorial shows a direct client pattern that keeps those two concepts separate.\n\nReserp accepts a complete Google Search URL in one JSON field:\n\n```\ncurl -X POST https://api.reserp.ai/v1/serp \\\n  -H \"Authorization: Bearer $RESERP_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://www.google.com/search?q=photonic+computing&gl=us&hl=en\"}'\n```\n\nThe URL can use normal Google parameters such as:\n\n| Parameter | Meaning | Example |\n|---|---|---|\n`q` |\nsearch query | `photonic computing` |\n`gl` |\ncountry | `us` |\n`hl` |\nlanguage | `en` |\n`start` |\norganic-result offset | `10` |\n`tbs` |\ntime or result filter | `qdr:w` |\n`tbm` |\nsearch type | `nws` |\n\nThe Google `num`\n\nparameter is unsupported and should not be sent.\n\nA successful response contains pagination independent of `results.length`\n\n:\n\n```\n{\n  \"pagination\": {\n    \"start\": 0,\n    \"nextStart\": 10,\n    \"nextUrl\": \"https://www.google.com/search?q=photonic+computing&gl=us&hl=en&start=10\"\n  }\n}\n```\n\nThe rules are simple:\n\n`start`\n\nor use `0`\n\nfor the first page;`10`\n\nfor the second page;`20`\n\nfor the third page;An invalid offset, such as `start=17`\n\n, produces a non-billable `400 invalid_request`\n\nresponse.\n\nThe transport function below makes exactly one request. It does not retry or fetch another page:\n\n``` python\nimport json\nimport os\nfrom urllib.request import Request, urlopen\n\ndef search_once(google_url: str) -> dict:\n    request = Request(\n        \"https://api.reserp.ai/v1/serp\",\n        data=json.dumps({\"url\": google_url}).encode(\"utf-8\"),\n        headers={\n            \"Authorization\": f\"Bearer {os.environ['RESERP_API_KEY']}\",\n            \"Content-Type\": \"application/json\",\n        },\n        method=\"POST\",\n    )\n\n    with urlopen(request) as response:\n        return json.load(response)\n```\n\nCall it for the first page:\n\n```\nfirst = search_once(\n    \"https://www.google.com/search\"\n    \"?q=photonic+computing\"\n    \"&gl=us\"\n    \"&hl=en\"\n)\n\nprint(first[\"pagination\"])\n```\n\nIf—and only if—the application decides it needs another page, pass the returned URL into another explicit invocation:\n\n```\nsecond_url = first[\"pagination\"][\"nextUrl\"]\nsecond = search_once(second_url)\n```\n\nThe important part is what the code does not say:\n\n```\n# Wrong: visible block count is not Google's organic offset.\nnext_start = len(first[\"results\"])\n```\n\nIt also avoids burying a loop inside `search_once`\n\n. The calling job may have its own page limit, request budget, cancellation signal, or queue. Those are application policies, not transport behavior.\n\nEach result block may include `text`\n\n, `url`\n\n, and `children`\n\n, but not every block has every field. In particular, `text`\n\nis optional in the public contract:\n\n```\nfor block in first.get(\"results\", []):\n    text = block.get(\"text\")\n    url = block.get(\"url\")\n\n    if text is not None:\n        print(text)\n    if url is not None:\n        print(url)\n```\n\nDo not flatten `children`\n\nunless your application has a reason to discard their URL boundaries. Reserp retains nesting precisely when flattening would lose descendant content or structure.\n\nEvery error body has four public fields:\n\n```\n{\n  \"ok\": false,\n  \"error\": \"service_unavailable\",\n  \"retryable\": true,\n  \"billed\": false\n}\n```\n\n`retryable`\n\nis authoritative for retry eligibility. `billed`\n\nreports whether billing settled; it does not decide whether an error is retryable.\n\nThe 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.\n\nKeep the three layers separate:\n\n```\nGoogle organic offset: start = 0, 10, 20, ...\nVisible page content:   results = heterogeneous blocks\nApplication policy:     whether and when to request another page\n```\n\nOnce those layers are separate, pagination is predictable. Follow `pagination.nextUrl`\n\n, never infer an offset from the number of returned blocks, and keep each API invocation explicit.\n\nThe complete contract, including supported Google parameters and stable error codes, is available in the [Reserp API documentation](https://reserp.ai/docs) and [OpenAPI JSON](https://reserp.ai/openapi.json).", "url": "https://wpnews.pro/news/google-search-pagination-is-not-results-length", "canonical_source": "https://dev.to/reserp/google-search-pagination-is-not-resultslength-5959", "published_at": "2026-08-20 04:04:12+00:00", "updated_at": "2026-08-20 04:43:25.278645+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Reserp", "Google Search"], "alternates": {"html": "https://wpnews.pro/news/google-search-pagination-is-not-results-length", "markdown": "https://wpnews.pro/news/google-search-pagination-is-not-results-length.md", "text": "https://wpnews.pro/news/google-search-pagination-is-not-results-length.txt", "jsonld": "https://wpnews.pro/news/google-search-pagination-is-not-results-length.jsonld"}}