Pagination

Serply passes Google's own pagination parameters straight through, so paging is offset-based: you ask for a page size with num and a starting offset with start. There are no cursors and no pagination envelope in the response.

Both parameters go inside the path-packed query string, alongside q:

curl --header 'X-Api-Key: YOUR_API_KEY' \
  'https://api.serply.io/v1/search/q=coffee&num=10&start=0'

Parameters

num

The number of results to return. Honored exactly for small values - num=5 returns 5 results.

Note that Google serves roughly 10 organic results per page, and Serply does not stitch pages together for you. Asking for more than that does not produce more: num=20, num=50, and num=100 all come back with about 10 results, the same as num=10. To collect 50 results you must make five requests at increasing offsets, not one request with num=50.

start

The zero-based offset into the result set. Omit it (or pass 0) for the first page, 10 for the second, and so on.

# first page
'https://api.serply.io/v1/search/q=coffee&num=10&start=0'

# second page
'https://api.serply.io/v1/search/q=coffee&num=10&start=10'

The response has no pagination metadata

Search responses contain no pagination object, no cursor, and no usable result count. The total field is present on some endpoints but is null for Google Search, so you cannot use it to compute a page count in advance.

This means there is no way to know how many pages exist before you request them. You page until you stop getting results.

Detecting the last page

When you page past the end of the result set, the API returns 200 OK with an empty results array:

{
  "results": [],
  "answers": [],
  "related_searches": { "text": [] }
}

An empty results array is not proof you have reached the end. The same response appears during transient upstream failures, which cluster with 502 responses. The two are indistinguishable from a single request.

To tell them apart, retry once or twice before concluding the page is empty. A genuine end-of-results is stable across retries; a transient failure is not.

Collecting multiple pages

A loop that stops on a confirmed-empty page:

import time
import requests

HEADERS = {'X-Api-Key': 'YOUR_API_KEY'}
BASE = 'https://api.serply.io/v1/search'


def fetch_page(query, start, num=10, retries=2):
    """Return one page. Retries so a transient blip isn't read as the end."""
    for attempt in range(retries + 1):
        r = requests.get(
            f'{BASE}/q={query}&num={num}&start={start}',
            headers=HEADERS,
            timeout=15,
        )
        r.raise_for_status()
        results = r.json().get('results', [])
        if results:
            return results
        if attempt < retries:
            time.sleep(1)
    return []


def search_all(query, max_results=50, num=10):
    collected, seen = [], set()

    for start in range(0, max_results, num):
        page = fetch_page(query, start, num)
        if not page:
            break  # confirmed empty after retries - end of results

        for item in page:
            if item['link'] not in seen:
                seen.add(item['link'])
                collected.append(item)

    return collected[:max_results]

Two details worth keeping:

Deduplicate by link. Adjacent pages occasionally repeat a result, because Google reshuffles slightly between requests. In testing, four pages of 9 results yielded 34 unique links rather than 36.

Pin X-Proxy-Location across the whole loop. If you omit it, individual requests may be served from different countries, and a page fetched from a German proxy will share almost nothing with one fetched from a US proxy. That inflates your unique-result count with what looks like new data but is really a different regional SERP:

HEADERS = {
    'X-Api-Key': 'YOUR_API_KEY',
    'X-Proxy-Location': 'US',
}

Setting it once for every request in the loop keeps all pages on the same result set. The Webhooks guide has the measurements.

Always set an upper bound. Since there is no total to check against, a loop without a max_results ceiling will keep paging - and keep billing - on any query where the empty-page signal is delayed.

Billing

Each page is a separate request, so each page costs one credit. Paging to 50 results costs 5 credits, not 1.

Cached responses are free, and most endpoints cache for several minutes, so re-running the same paged query while developing does not bill again. See Pricing for credit rates.

Other endpoints

num and start follow Google's query-string conventions and apply to the Google Search endpoint. Other resources use their own parameters - Reddit listings take limit and an after token, and Google Maps takes num after a ?. Check the relevant page under API Endpoints before assuming start applies.