# 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`:

```bash
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.

```bash
# 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:

```json
{
  "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:

```python
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:

```python
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](/docs/guides/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](/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](/docs) before assuming
`start` applies.
